home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2008 February / PCWFEB08.iso / Software / Freeware / Miro 1.0 / Miro_Installer.exe / Miro_Downloader.exe / app.pyc (.txt) < prev    next >
Encoding:
Python Compiled Bytecode  |  2007-11-12  |  87.7 KB  |  2,768 lines

  1. # Source Generated with Decompyle++
  2. # File: in.pyc (Python 2.5)
  3.  
  4. import config
  5. import prefs
  6. import database
  7. db = database.defaultDatabase
  8. import views
  9. import indexes
  10. import sorts
  11. import maps
  12. import menu
  13. import util
  14. import feed
  15. import item
  16. import playlist
  17. import tabs
  18. import opml
  19. import folder
  20. import autodler
  21. import databaseupgrade
  22. import resources
  23. import selection
  24. import template
  25. import singleclick
  26. import storedatabase
  27. import subscription
  28. import downloader
  29. import autoupdate
  30. import xhtmltools
  31. import guide
  32. import idlenotifier
  33. import eventloop
  34. import searchengines
  35. import download_utils
  36. import os
  37. import re
  38. import shutil
  39. import cgi
  40. import traceback
  41. import threading
  42. import platform
  43. import dialogs
  44. import iconcache
  45. import moviedata
  46. import platformutils
  47. import logging
  48. import theme
  49. from string import Template
  50. import templatehelper
  51. import databasehelper
  52. import urllib
  53. import menubar
  54. from gtcache import gettext as _
  55. from gtcache import ngettext
  56. from clock import clock
  57. controller = None
  58. delegate = None
  59.  
  60. def main():
  61.     platformutils.setupLogging()
  62.     util.setupLogging()
  63.     Controller().Run()
  64.  
  65.  
  66. def start():
  67.     platformutils.setupLogging()
  68.     util.setupLogging()
  69.     Controller().runNonblocking()
  70.  
  71.  
  72. def startupFunction(func):
  73.     '''Decorator for startup functions.  If they throw an exception, miro will
  74.     show a error dialog and quit.
  75.     '''
  76.     
  77.     def wrapped(*args, **kwargs):
  78.         
  79.         try:
  80.             func(*args, **kwargs)
  81.         except:
  82.             util.failedExn('while finishing starting up')
  83.             frontend.exit(1)
  84.  
  85.  
  86.     return wrapped
  87.  
  88.  
  89. class PlaybackControllerBase:
  90.     
  91.     def __init__(self):
  92.         self.currentPlaylist = None
  93.         self.justPlayOne = False
  94.         self.currentItem = None
  95.         self.updateVideoTimeDC = None
  96.  
  97.     
  98.     def configure(self, view, firstItemId = None, justPlayOne = False):
  99.         self.currentPlaylist = Playlist(view, firstItemId)
  100.         self.justPlayOne = justPlayOne
  101.  
  102.     
  103.     def reset(self):
  104.         if self.currentPlaylist is not None:
  105.             eventloop.addIdle(self.currentPlaylist.reset, 'Reset Playlist')
  106.             self.currentPlaylist = None
  107.         
  108.  
  109.     
  110.     def configureWithSelection(self):
  111.         itemSelection = controller.selection.itemListSelection
  112.         view = itemSelection.currentView
  113.         if itemSelection.currentView is None:
  114.             return None
  115.         
  116.         for item in view:
  117.             itemid = item.getID()
  118.             if itemSelection.isSelected(view, itemid) and item.isDownloaded():
  119.                 self.configure(view, itemid)
  120.                 break
  121.                 continue
  122.         
  123.  
  124.     
  125.     def enterPlayback(self):
  126.         if self.currentPlaylist is None:
  127.             self.configureWithSelection()
  128.         
  129.         if self.currentPlaylist is not None:
  130.             startItem = self.currentPlaylist.cur()
  131.             if startItem is not None:
  132.                 self.playItem(startItem)
  133.             
  134.         
  135.  
  136.     
  137.     def exitPlayback(self, switchDisplay = True):
  138.         self.reset()
  139.         if switchDisplay:
  140.             controller.selection.displayCurrentTabContent()
  141.         
  142.  
  143.     
  144.     def playPause(self):
  145.         videoDisplay = controller.videoDisplay
  146.         frame = controller.frame
  147.         if frame.getDisplay(frame.mainDisplay) == videoDisplay:
  148.             videoDisplay.playPause()
  149.         else:
  150.             self.enterPlayback()
  151.  
  152.     
  153.     def pause(self):
  154.         videoDisplay = controller.videoDisplay
  155.         frame = controller.frame
  156.         if frame.getDisplay(frame.mainDisplay) == videoDisplay:
  157.             videoDisplay.pause()
  158.         
  159.  
  160.     
  161.     def removeItem(self, item):
  162.         if item.idExists():
  163.             item.executeExpire()
  164.         
  165.  
  166.     
  167.     def playItem(self, anItem):
  168.         
  169.         try:
  170.             if self.currentItem:
  171.                 self.currentItem.onViewedCancel()
  172.             
  173.             self.currentItem = None
  174.             while not os.path.exists(anItem.getVideoFilename()):
  175.                 logging.info("movie file '%s' is missing, skipping to next", anItem.getVideoFilename())
  176.                 eventloop.addIdle(self.removeItem, 'Remove deleted item', args = (anItem.item,))
  177.                 anItem = self.currentPlaylist.getNext()
  178.                 if anItem is None:
  179.                     self.stop()
  180.                     return None
  181.                     continue
  182.             self.currentItem = anItem
  183.             if anItem is not None:
  184.                 videoDisplay = controller.videoDisplay
  185.                 videoRenderer = videoDisplay.getRendererForItem(anItem)
  186.                 if videoRenderer is not None:
  187.                     self.playItemInternally(anItem, videoDisplay, videoRenderer)
  188.                 else:
  189.                     frame = controller.frame
  190.                     if frame.getDisplay(frame.mainDisplay) is videoDisplay:
  191.                         if videoDisplay.isFullScreen:
  192.                             videoDisplay.exitFullScreen()
  193.                         
  194.                         videoDisplay.stop()
  195.                     
  196.                     self.scheduleExternalPlayback(anItem)
  197.         except:
  198.             util.failedExn('when trying to play a video')
  199.             self.stop()
  200.  
  201.  
  202.     
  203.     def playItemInternally(self, anItem, videoDisplay, videoRenderer):
  204.         logging.info('Playing item with renderer: %s' % videoRenderer)
  205.         controller.videoDisplay.setExternal(False)
  206.         frame = controller.frame
  207.         if frame.getDisplay(frame.mainDisplay) is not videoDisplay:
  208.             frame.selectDisplay(videoDisplay, frame.mainDisplay)
  209.         
  210.         videoDisplay.selectItem(anItem, videoRenderer)
  211.         if config.get(prefs.RESUME_VIDEOS_MODE) and anItem.resumeTime > 10:
  212.             videoDisplay.playFromTime(anItem.resumeTime)
  213.         else:
  214.             videoDisplay.play()
  215.         self.startUpdateVideoTime()
  216.  
  217.     
  218.     def playItemExternally(self, itemID):
  219.         anItem = mapToPlaylistItem(db.getObjectByID(int(itemID)))
  220.         controller.videoInfoItem = anItem
  221.         newDisplay = TemplateDisplay('external-playback-continue', 'default')
  222.         frame = controller.frame
  223.         frame.selectDisplay(newDisplay, frame.mainDisplay)
  224.         return anItem
  225.  
  226.     
  227.     def scheduleExternalPlayback(self, anItem):
  228.         controller.videoDisplay.setExternal(True)
  229.         controller.videoDisplay.stopOnDeselect = False
  230.         controller.videoInfoItem = anItem
  231.         newDisplay = TemplateDisplay('external-playback', 'default')
  232.         frame = controller.frame
  233.         frame.selectDisplay(newDisplay, frame.mainDisplay)
  234.         anItem.markItemSeen()
  235.  
  236.     
  237.     def startUpdateVideoTime(self):
  238.         if not self.updateVideoTimeDC:
  239.             self.updateVideoTimeDC = eventloop.addTimeout(0.5, self.updateVideoTime, 'Update Video Time')
  240.         
  241.  
  242.     
  243.     def stopUpdateVideoTime(self):
  244.         if self.updateVideoTimeDC:
  245.             self.updateVideoTimeDC.cancel()
  246.             self.updateVideoTimeDC = None
  247.         
  248.  
  249.     
  250.     def updateVideoTime(self, repeat = True):
  251.         t = controller.videoDisplay.getCurrentTime()
  252.         if t != None and self.currentItem:
  253.             self.currentItem.setResumeTime(t)
  254.         
  255.         if repeat:
  256.             self.updateVideoTimeDC = eventloop.addTimeout(0.5, self.updateVideoTime, 'Update Video Time')
  257.         
  258.  
  259.     
  260.     def stop(self, switchDisplay = True, markAsViewed = False):
  261.         controller.videoDisplay.setExternal(False)
  262.         if self.updateVideoTimeDC:
  263.             self.updateVideoTime(repeat = False)
  264.             self.stopUpdateVideoTime()
  265.         
  266.         if self.currentItem:
  267.             self.currentItem.onViewedCancel()
  268.         
  269.         self.currentItem = None
  270.         frame = controller.frame
  271.         videoDisplay = controller.videoDisplay
  272.         if frame.getDisplay(frame.mainDisplay) == videoDisplay:
  273.             videoDisplay.stop()
  274.         
  275.         self.exitPlayback(switchDisplay)
  276.  
  277.     
  278.     def skip(self, direction, allowMovieReset = True):
  279.         frame = controller.frame
  280.         currentDisplay = frame.getDisplay(frame.mainDisplay)
  281.         if self.currentPlaylist is None:
  282.             self.stop()
  283.         elif allowMovieReset and direction == -1 and hasattr(currentDisplay, 'getCurrentTime') and currentDisplay.getCurrentTime() > 2:
  284.             currentDisplay.goToBeginningOfMovie()
  285.         elif config.get(prefs.SINGLE_VIDEO_PLAYBACK_MODE) or self.justPlayOne:
  286.             self.stop()
  287.         elif direction == 1:
  288.             nextItem = self.currentPlaylist.getNext()
  289.         else:
  290.             nextItem = self.currentPlaylist.getPrev()
  291.         if nextItem is None:
  292.             self.stop()
  293.         elif self.updateVideoTimeDC:
  294.             self.updateVideoTime(repeat = False)
  295.             self.stopUpdateVideoTime()
  296.         
  297.         self.playItem(nextItem)
  298.  
  299.     
  300.     def onMovieFinished(self):
  301.         self.stopUpdateVideoTime()
  302.         setToStart = False
  303.         if self.currentItem:
  304.             self.currentItem.setResumeTime(0)
  305.             if self.currentItem.getFeedURL() == 'dtv:singleFeed':
  306.                 setToStart = True
  307.             
  308.         
  309.         if setToStart:
  310.             frame = controller.frame
  311.             currentDisplay = frame.getDisplay(frame.mainDisplay)
  312.             currentDisplay.pause()
  313.             currentDisplay.goToBeginningOfMovie()
  314.             currentDisplay.pause()
  315.         else:
  316.             return self.skip(1, False)
  317.  
  318.  
  319.  
  320. class Display:
  321.     """Base class representing a display in a MainFrame's right-hand pane."""
  322.     
  323.     def __init__(self):
  324.         self.currentFrame = None
  325.  
  326.     
  327.     def isSelected(self):
  328.         return self.currentFrame is not None
  329.  
  330.     
  331.     def onSelected(self, frame):
  332.         '''Called when the Display is shown in the given MainFrame.'''
  333.         pass
  334.  
  335.     
  336.     def onDeselected(self, frame):
  337.         '''Called when the Display is no longer shown in the given
  338.         MainFrame. This function is called on the Display losing the
  339.         selection before onSelected is called on the Display gaining the
  340.         selection.'''
  341.         pass
  342.  
  343.     
  344.     def onSelected_private(self, frame):
  345.         if not self.currentFrame == None:
  346.             raise AssertionError
  347.         self.currentFrame = frame
  348.  
  349.     
  350.     def onDeselected_private(self, frame):
  351.         if not self.currentFrame == frame:
  352.             raise AssertionError
  353.         self.currentFrame = None
  354.  
  355.     
  356.     def callWhenReadyToDisplay(self, hook):
  357.         hook()
  358.  
  359.     
  360.     def cancel(self):
  361.         '''Called when the Display is not shown because it is not ready yet
  362.         and another display will take its place'''
  363.         pass
  364.  
  365.     
  366.     def getWatchable(self):
  367.         '''Subclasses can implement this if they can return a database view
  368.         of watchable items'''
  369.         return None
  370.  
  371.  
  372.  
  373. class VideoDisplayBase(Display):
  374.     
  375.     def __init__(self):
  376.         Display.__init__(self)
  377.         self.playbackController = None
  378.         self.volume = 1
  379.         self.previousVolume = 1
  380.         self.isPlaying = False
  381.         self.isFullScreen = False
  382.         self.isExternal = False
  383.         self.stopOnDeselect = True
  384.         self.renderers = list()
  385.         self.activeRenderer = None
  386.  
  387.     
  388.     def initRenderers(self):
  389.         pass
  390.  
  391.     
  392.     def setExternal(self, external):
  393.         self.isExternal = external
  394.  
  395.     
  396.     def fillMovieData(self, filename, movie_data, callback):
  397.         for renderer in self.renderers:
  398.             success = renderer.fillMovieData(filename, movie_data)
  399.             if success:
  400.                 callback()
  401.                 return None
  402.                 continue
  403.         
  404.         callback()
  405.  
  406.     
  407.     def getRendererForItem(self, anItem):
  408.         for renderer in self.renderers:
  409.             if renderer.canPlayItem(anItem):
  410.                 return renderer
  411.                 continue
  412.         
  413.  
  414.     
  415.     def canPlayItem(self, anItem):
  416.         return self.getRendererForItem(anItem) is not None
  417.  
  418.     
  419.     def canPlayFile(self, filename):
  420.         for renderer in self.renderers:
  421.             if renderer.canPlayFile(filename):
  422.                 return True
  423.                 continue
  424.         
  425.         return False
  426.  
  427.     
  428.     def selectItem(self, anItem, renderer):
  429.         self.stopOnDeselect = True
  430.         controller.videoInfoItem = anItem
  431.         templ = TemplateDisplay('video-info', 'default')
  432.         area = controller.frame.videoInfoDisplay
  433.         controller.frame.selectDisplay(templ, area)
  434.         self.setActiveRenderer(renderer)
  435.         self.activeRenderer.selectItem(anItem)
  436.         self.activeRenderer.setVolume(self.getVolume())
  437.  
  438.     
  439.     def setActiveRenderer(self, renderer):
  440.         self.activeRenderer = renderer
  441.  
  442.     
  443.     def reset(self):
  444.         self.isPlaying = False
  445.         self.stopOnDeselect = True
  446.         if self.activeRenderer is not None:
  447.             self.activeRenderer.reset()
  448.         
  449.         self.activeRenderer = None
  450.  
  451.     
  452.     def goToBeginningOfMovie(self):
  453.         if self.activeRenderer is not None:
  454.             self.activeRenderer.goToBeginningOfMovie()
  455.         
  456.  
  457.     
  458.     def playPause(self):
  459.         if self.isPlaying:
  460.             self.pause()
  461.         else:
  462.             self.play()
  463.  
  464.     
  465.     def playFromTime(self, startTime):
  466.         if self.activeRenderer is not None:
  467.             self.activeRenderer.playFromTime(startTime)
  468.         
  469.         self.isPlaying = True
  470.  
  471.     
  472.     def play(self):
  473.         if self.activeRenderer is not None:
  474.             self.activeRenderer.play()
  475.         
  476.         self.isPlaying = True
  477.  
  478.     
  479.     def pause(self):
  480.         if self.activeRenderer is not None:
  481.             self.activeRenderer.pause()
  482.         
  483.         self.isPlaying = False
  484.  
  485.     
  486.     def stop(self):
  487.         if self.isFullScreen:
  488.             self.exitFullScreen()
  489.         
  490.         if self.activeRenderer is not None:
  491.             self.activeRenderer.stop()
  492.         
  493.         self.reset()
  494.  
  495.     
  496.     def goFullScreen(self):
  497.         self.isFullScreen = True
  498.         if not self.isPlaying:
  499.             self.play()
  500.         
  501.  
  502.     
  503.     def exitFullScreen(self):
  504.         self.isFullScreen = False
  505.  
  506.     
  507.     def getCurrentTime(self):
  508.         if self.activeRenderer is not None:
  509.             return self.activeRenderer.getCurrentTime()
  510.         
  511.  
  512.     
  513.     def setCurrentTime(self, seconds):
  514.         if self.activeRenderer is not None:
  515.             self.activeRenderer.setCurrentTime(seconds)
  516.         
  517.  
  518.     
  519.     def getProgress(self):
  520.         if self.activeRenderer is not None:
  521.             return self.activeRenderer.getProgress()
  522.         
  523.         return 0
  524.  
  525.     
  526.     def setProgress(self, progress):
  527.         if self.activeRenderer is not None:
  528.             return self.activeRenderer.setProgress(progress)
  529.         
  530.  
  531.     
  532.     def getDuration(self):
  533.         if self.activeRenderer is not None:
  534.             return self.activeRenderer.getDuration()
  535.         
  536.  
  537.     
  538.     def setVolume(self, level):
  539.         if level > 1:
  540.             level = 1
  541.         
  542.         if level < 0:
  543.             level = 0
  544.         
  545.         self.volume = level
  546.         config.set(prefs.VOLUME_LEVEL, level)
  547.         if self.activeRenderer is not None:
  548.             self.activeRenderer.setVolume(level)
  549.         
  550.  
  551.     
  552.     def getVolume(self):
  553.         return self.volume
  554.  
  555.     
  556.     def muteVolume(self):
  557.         self.previousVolume = self.getVolume()
  558.         self.setVolume(0)
  559.  
  560.     
  561.     def restoreVolume(self):
  562.         self.setVolume(self.previousVolume)
  563.  
  564.     
  565.     def onDeselected(self, frame):
  566.         if self.isPlaying and self.stopOnDeselect:
  567.             controller.playbackController.stop(False)
  568.         
  569.  
  570.  
  571.  
  572. class VideoRenderer:
  573.     
  574.     def __init__(self):
  575.         self.interactivelySeeking = False
  576.  
  577.     
  578.     def canPlayItem(self, anItem):
  579.         return self.canPlayFile(anItem.getVideoFilename())
  580.  
  581.     
  582.     def canPlayFile(self, filename):
  583.         return False
  584.  
  585.     
  586.     def fillMovieData(self, filename, movie_data):
  587.         return False
  588.  
  589.     
  590.     def getDisplayTime(self):
  591.         seconds = self.getCurrentTime()
  592.         return util.formatTimeForUser(seconds)
  593.  
  594.     
  595.     def getDisplayDuration(self):
  596.         seconds = self.getDuration()
  597.         return util.formatTimeForUser(seconds)
  598.  
  599.     
  600.     def getDisplayRemainingTime(self):
  601.         seconds = abs(self.getCurrentTime() - self.getDuration())
  602.         return util.formatTimeForUser(seconds, -1)
  603.  
  604.     
  605.     def getProgress(self):
  606.         duration = self.getDuration()
  607.         if duration == 0 or duration == None:
  608.             return 0
  609.         
  610.         return self.getCurrentTime() / duration
  611.  
  612.     
  613.     def setProgress(self, progress):
  614.         if progress > 1:
  615.             progress = 1
  616.         
  617.         if progress < 0:
  618.             progress = 0
  619.         
  620.         self.setCurrentTime(self.getDuration() * progress)
  621.  
  622.     
  623.     def selectItem(self, anItem):
  624.         self.selectFile(anItem.getVideoFilename())
  625.  
  626.     
  627.     def selectFile(self, filename):
  628.         pass
  629.  
  630.     
  631.     def reset(self):
  632.         pass
  633.  
  634.     
  635.     def setCurrentTime(self, seconds):
  636.         pass
  637.  
  638.     
  639.     def getDuration(self):
  640.         return 0
  641.  
  642.     
  643.     def setVolume(self, level):
  644.         pass
  645.  
  646.     
  647.     def goToBeginningOfMovie(self):
  648.         pass
  649.  
  650.     
  651.     def getCurrentTime(self):
  652.         pass
  653.  
  654.     
  655.     def playFromTime(self, position):
  656.         self.play()
  657.         self.setCurrentTime(position)
  658.  
  659.     
  660.     def play(self):
  661.         pass
  662.  
  663.     
  664.     def pause(self):
  665.         pass
  666.  
  667.     
  668.     def stop(self):
  669.         pass
  670.  
  671.     
  672.     def getRate(self):
  673.         return 1
  674.  
  675.     
  676.     def setRate(self, rate):
  677.         pass
  678.  
  679.     
  680.     def movieDataProgramInfo(self, videoPath, thumbnailPath):
  681.         raise NotImplementedError()
  682.  
  683.  
  684. import frontend
  685.  
  686. class Controller(frontend.Application):
  687.     
  688.     def __init__(self):
  689.         global controller, delegate
  690.         frontend.Application.__init__(self)
  691.         if not controller is None:
  692.             raise AssertionError
  693.         if not delegate is None:
  694.             raise AssertionError
  695.         controller = self
  696.         delegate = frontend.UIBackendDelegate()
  697.         self.frame = None
  698.         self.inQuit = False
  699.         self.guideURL = None
  700.         self.guide = None
  701.         self.initial_feeds = False
  702.         self.finishedStartup = False
  703.         self.idlingNotifier = None
  704.         self.gatheredVideos = None
  705.         self.sendingCrashReport = 0
  706.         self.librarySearchTerm = None
  707.         self.newVideosSearchTerm = None
  708.  
  709.     
  710.     def onStartup(self, gatheredVideos = None):
  711.         logging.info('Starting up %s', config.get(prefs.LONG_APP_NAME))
  712.         logging.info('Version:    %s', config.get(prefs.APP_VERSION))
  713.         logging.info('Revision:   %s', config.get(prefs.APP_REVISION))
  714.         logging.info('Builder:    %s', config.get(prefs.BUILD_MACHINE))
  715.         logging.info('Build Time: %s', config.get(prefs.BUILD_TIME))
  716.         util.print_mem_usage('Pre everything memory check')
  717.         logging.info('Loading preferences...')
  718.         config.load()
  719.         config.addChangeCallback(self.configDidChange)
  720.         feed.setDelegate(delegate)
  721.         feed.setSortFunc(sorts.item)
  722.         autoupdate.setDelegate(delegate)
  723.         database.setDelegate(delegate)
  724.         dialogs.setDelegate(delegate)
  725.         if not config.get(prefs.STARTUP_TASKS_DONE):
  726.             logging.info('Showing startup dialog...')
  727.             delegate.performStartupTasks(self.finishStartup)
  728.             config.set(prefs.STARTUP_TASKS_DONE, True)
  729.             config.save()
  730.         else:
  731.             self.finishStartup(gatheredVideos)
  732.         logging.info('Starting event loop thread')
  733.         eventloop.startup()
  734.  
  735.     
  736.     def finishStartup(self, gatheredVideos = None):
  737.         self.gatheredVideos = gatheredVideos
  738.         eventloop.addUrgentCall(self.initializeDatabase, 'Initializing database')
  739.  
  740.     
  741.     def initializeDatabase(self):
  742.         
  743.         try:
  744.             views.initialize()
  745.             util.print_mem_usage('Pre-database memory check:')
  746.             logging.info('Restoring database...')
  747.             database.defaultDatabase.liveStorage = storedatabase.LiveStorage()
  748.             db.recomputeFilters()
  749.             eventloop.addUrgentCall(self.checkMoviesDirectoryGone, 'checking movies directory')
  750.         except databaseupgrade.DatabaseTooNewError:
  751.             title = _('Database too new')
  752.             description = Template(_('You have a database that was saved with a newer version of $shortAppName. You must download the latest version of $shortAppName and run that.')).substitute(shortAppName = config.get(prefs.SHORT_APP_NAME))
  753.             
  754.             def callback(dialog):
  755.                 eventloop.quit()
  756.                 frontend.quit(True)
  757.  
  758.             dialogs.MessageBoxDialog(title, description).run(callback)
  759.  
  760.  
  761.     initializeDatabase = startupFunction(initializeDatabase)
  762.     
  763.     def checkMoviesDirectoryGone(self):
  764.         if not self.moviesDirectoryGone():
  765.             eventloop.addUrgentCall(self.finalizeStartup, 'finalizing startup')
  766.             return None
  767.         
  768.         title = _('Video Directory Missing')
  769.         description = _("\nMiro can't find your primary video directory.  This may be because it's located on an external drive that is currently disconnected.\n\nIf you continue, the video directory will be reset to a location on this drive (this will cause you to lose some details about the videos on the external drive).  You can also quit, connect the drive, and relaunch Miro.")
  770.         dialog = dialogs.ChoiceDialog(title, description, dialogs.BUTTON_QUIT, dialogs.BUTTON_LAUNCH_MIRO)
  771.         
  772.         def callback(dialog):
  773.             if dialog.choice == dialogs.BUTTON_LAUNCH_MIRO:
  774.                 eventloop.addUrgentCall(self.finalizeStartup, 'finalizing startup')
  775.             else:
  776.                 eventloop.quit()
  777.                 frontend.quit(True)
  778.  
  779.         dialog.run(callback)
  780.  
  781.     checkMoviesDirectoryGone = startupFunction(checkMoviesDirectoryGone)
  782.     
  783.     def finalizeStartup(self):
  784.         downloader.startupDownloader()
  785.         util.print_mem_usage('Post-downloader memory check')
  786.         self.setupGlobalFeed(u'dtv:manualFeed', initiallyAutoDownloadable = False)
  787.         self.setupGlobalFeed(u'dtv:singleFeed', initiallyAutoDownloadable = False)
  788.         self.setupGlobalFeed(u'dtv:search', initiallyAutoDownloadable = False)
  789.         self.setupGlobalFeed(u'dtv:searchDownloads')
  790.         tabs.reloadStaticTabs()
  791.         
  792.         try:
  793.             channelTabOrder = util.getSingletonDDBObject(views.channelTabOrder)
  794.         except LookupError:
  795.             logging.info('Creating channel tab order')
  796.             channelTabOrder = tabs.TabOrder(u'channel')
  797.  
  798.         
  799.         try:
  800.             playlistTabOrder = util.getSingletonDDBObject(views.playlistTabOrder)
  801.         except LookupError:
  802.             logging.info('Creating playlist tab order')
  803.             playlistTabOrder = tabs.TabOrder(u'playlist')
  804.  
  805.         searchengines.createEngines()
  806.         (newGuide, channelGuide) = _getInitialChannelGuide()
  807.         _getThemeHistory()
  808.         if newGuide:
  809.             if config.get(prefs.MAXIMIZE_ON_FIRST_RUN).lower() not in ('false', 'no', '0'):
  810.                 delegate.maximizeWindow()
  811.             
  812.             for temp_guide in unicode(config.get(prefs.ADDITIONAL_CHANNEL_GUIDES)).split():
  813.                 if views.guides.getItemWithIndex(indexes.guidesByURL, temp_guide) is None:
  814.                     guide.ChannelGuide(temp_guide)
  815.                     continue
  816.             
  817.         
  818.         self.newTab = None
  819.         self.downloadTab = None
  820.         for tab in views.allTabs:
  821.             if tab.tabTemplateBase == 'newtab':
  822.                 self.newTab = tab
  823.                 continue
  824.             if tab.tabTemplateBase == 'downloadtab':
  825.                 self.downloadTab = tab
  826.                 continue
  827.         
  828.         views.unwatchedItems.addAddCallback(self.onUnwatchedItemsCountChange)
  829.         views.unwatchedItems.addRemoveCallback(self.onUnwatchedItemsCountChange)
  830.         views.downloadingItems.addAddCallback(self.onDownloadingItemsCountChange)
  831.         views.downloadingItems.addRemoveCallback(self.onDownloadingItemsCountChange)
  832.         self.onUnwatchedItemsCountChange(None, None)
  833.         self.onDownloadingItemsCountChange(None, None)
  834.         self.setupGlobalFeed(u'dtv:directoryfeed')
  835.         logging.info('Spawning auto downloader...')
  836.         autodler.startDownloader()
  837.         if config.get(prefs.LIMIT_UPSTREAM) is True:
  838.             logging.info('Spawning idle notifier')
  839.             self.idlingNotifier = idlenotifier.IdleNotifier(self)
  840.             self.idlingNotifier.start()
  841.         
  842.         self.playbackController = frontend.PlaybackController()
  843.         util.print_mem_usage('Pre-UI memory check')
  844.         logging.info('Displaying main frame...')
  845.         self.frame = frontend.MainFrame(self)
  846.         logging.info('Creating video display...')
  847.         self.videoDisplay = frontend.VideoDisplay()
  848.         self.videoDisplay.initRenderers()
  849.         self.videoDisplay.playbackController = self.playbackController
  850.         self.videoDisplay.setVolume(config.get(prefs.VOLUME_LEVEL))
  851.         util.print_mem_usage('Post-UI memory check')
  852.         self.selection = selection.SelectionHandler()
  853.         self.selection.selectFirstGuide()
  854.         if self.initial_feeds:
  855.             views.feedTabs.resetCursor()
  856.             tab = views.feedTabs.getNext()
  857.             if tab is not None:
  858.                 self.selection.selectTabByObject(tab.obj)
  859.             
  860.         
  861.         util.print_mem_usage('Post-selection memory check')
  862.         item.reconnectDownloaders()
  863.         util.print_mem_usage('Post-item reconnect memory check')
  864.         eventloop.addTimeout(3, autoupdate.checkForUpdates, 'Check for updates')
  865.         feed.expireItems()
  866.         self.tabDisplay = TemplateDisplay('tablist', 'default', playlistTabOrder = playlistTabOrder, channelTabOrder = channelTabOrder)
  867.         self.frame.selectDisplay(self.tabDisplay, self.frame.channelsDisplay)
  868.         self.updateAvailableItemsCountFeedback()
  869.         if self.gatheredVideos is not None and len(self.gatheredVideos) > 0:
  870.             singleclick.resetCommandLineView()
  871.             for v in self.gatheredVideos:
  872.                 
  873.                 try:
  874.                     singleclick.addVideo(v)
  875.                 continue
  876.                 except Exception:
  877.                     e = None
  878.                     logging.info('error while adding file %s', v)
  879.                     logging.info(e)
  880.                     continue
  881.                 
  882.  
  883.             
  884.         
  885.         util.print_mem_usage('Pre single-click memory check')
  886.         eventloop.addIdle(singleclick.parseCommandLineArgs, 'parse command line')
  887.         util.print_mem_usage('Post single-click memory check')
  888.         starttime = clock()
  889.         iconcache.clearOrphans()
  890.         logging.timing('Icon clear: %.3f', clock() - starttime)
  891.         logging.info('Starting movie data updates')
  892.         moviedata.movieDataUpdater.startThread()
  893.         logging.info('Finished startup sequence')
  894.         self.finishStartupSequence()
  895.  
  896.     finalizeStartup = startupFunction(finalizeStartup)
  897.     
  898.     def finishStartupSequence(self):
  899.         self.finishedStartup = True
  900.         frontend.Application.finishStartupSequence(self)
  901.  
  902.     
  903.     def setupGlobalFeed(self, url, *args, **kwargs):
  904.         feedView = views.feeds.filterWithIndex(indexes.feedsByURL, url)
  905.         
  906.         try:
  907.             if feedView.len() == 0:
  908.                 logging.info('Spawning global feed %s', url)
  909.                 d = feed.Feed(url, *args, **kwargs)
  910.             elif feedView.len() > 1:
  911.                 allFeeds = [ f for f in feedView ]
  912.                 for extra in allFeeds[1:]:
  913.                     extra.remove()
  914.                 
  915.                 util.failed('Too many db objects for %s' % url)
  916.         finally:
  917.             feedView.unlink()
  918.  
  919.  
  920.     
  921.     def moviesDirectoryGone(self):
  922.         movies_dir = config.get(prefs.MOVIES_DIRECTORY)
  923.         if not movies_dir.endswith(os.path.sep):
  924.             movies_dir += os.path.sep
  925.         
  926.         
  927.         try:
  928.             contents = os.listdir(movies_dir)
  929.         except OSError:
  930.             return True
  931.  
  932.         if contents != []:
  933.             return False
  934.         
  935.         for downloader in views.remoteDownloads:
  936.             if downloader.isFinished() and downloader.getFilename().startswith(movies_dir):
  937.                 return True
  938.                 continue
  939.         
  940.         return False
  941.  
  942.     
  943.     def getGlobalFeed(self, url):
  944.         feedView = views.feeds.filterWithIndex(indexes.feedsByURL, url)
  945.         rv = feedView[0]
  946.         feedView.unlink()
  947.         return rv
  948.  
  949.     
  950.     def removeGlobalFeed(self, url):
  951.         feedView = views.feeds.filterWithIndex(indexes.feedsByURL, url)
  952.         feedView.resetCursor()
  953.         nextfeed = feedView.getNext()
  954.         feedView.unlink()
  955.         if nextfeed is not None:
  956.             logging.info('Removing global feed %s', url)
  957.             nextfeed.remove()
  958.         
  959.  
  960.     
  961.     def copyCurrentFeedURL(self):
  962.         tabs = self.selection.getSelectedTabs()
  963.         if len(tabs) == 1 and tabs[0].isFeed():
  964.             delegate.copyTextToClipboard(tabs[0].obj.getURL())
  965.         
  966.  
  967.     
  968.     def recommendCurrentFeed(self):
  969.         tabs = self.selection.getSelectedTabs()
  970.         if len(tabs) == 1 and tabs[0].isFeed():
  971.             feed = tabs[0].obj
  972.             query = urllib.urlencode({
  973.                 'url': feed.getURL(),
  974.                 'title': feed.getTitle() })
  975.             delegate.openExternalURL('http://www.videobomb.com/democracy_channel/email_friend?%s' % (query,))
  976.         
  977.  
  978.     
  979.     def copyCurrentItemURL(self):
  980.         tabs = self.selection.getSelectedItems()
  981.         if len(tabs) == 1 and isinstance(tabs[0], item.Item):
  982.             url = tabs[0].getURL()
  983.             if url:
  984.                 delegate.copyTextToClipboard(url)
  985.             
  986.         
  987.  
  988.     
  989.     def selectAllItems(self):
  990.         self.selection.itemListSelection.selectAll()
  991.         self.selection.setTabListActive(False)
  992.  
  993.     
  994.     def removeCurrentSelection(self):
  995.         if self.selection.tabListActive:
  996.             selection = self.selection.tabListSelection
  997.         else:
  998.             selection = self.selection.itemListSelection
  999.         seltype = selection.getType()
  1000.         if seltype == 'channeltab':
  1001.             self.removeCurrentFeed()
  1002.         elif seltype == 'addedguidetab':
  1003.             self.removeCurrentGuide()
  1004.         elif seltype == 'playlisttab':
  1005.             self.removeCurrentPlaylist()
  1006.         elif seltype == 'item':
  1007.             self.removeCurrentItems()
  1008.         
  1009.  
  1010.     
  1011.     def removeCurrentFeed(self):
  1012.         pass
  1013.  
  1014.     
  1015.     def removeCurrentGuide(self):
  1016.         if self.selection.tabListSelection.getType() == 'addedguidetab':
  1017.             guides = [ t.obj for t in self.selection.getSelectedTabs() ]
  1018.             self.removeGuide(guides[0])
  1019.         
  1020.  
  1021.     
  1022.     def removeCurrentPlaylist(self):
  1023.         pass
  1024.  
  1025.     
  1026.     def removeCurrentItems(self):
  1027.         if self.selection.itemListSelection.getType() != 'item':
  1028.             return None
  1029.         
  1030.         selected = self.selection.getSelectedItems()
  1031.  
  1032.     
  1033.     def renameCurrentTab(self, typeCheckList = None):
  1034.         selected = self.selection.getSelectedTabs()
  1035.         if len(selected) != 1:
  1036.             return None
  1037.         
  1038.         obj = selected[0].obj
  1039.         if typeCheckList is None:
  1040.             typeCheckList = (playlist.SavedPlaylist, folder.ChannelFolder, folder.PlaylistFolder, feed.Feed)
  1041.         
  1042.         if obj.__class__ in typeCheckList:
  1043.             obj.rename()
  1044.         else:
  1045.             logging.warning('Bad object type in renameCurrentTab() %s', obj.__class__)
  1046.  
  1047.     
  1048.     def renameCurrentChannel(self):
  1049.         self.renameCurrentTab(typeCheckList = [
  1050.             feed.Feed,
  1051.             folder.ChannelFolder])
  1052.  
  1053.     
  1054.     def renameCurrentPlaylist(self):
  1055.         self.renameCurrentTab(typeCheckList = [
  1056.             playlist.SavedPlaylist,
  1057.             folder.PlaylistFolder])
  1058.  
  1059.     
  1060.     def downloadCurrentItems(self):
  1061.         selected = self.selection.getSelectedItems()
  1062.         downloadable = _[1]
  1063.         for item in downloadable:
  1064.             item.download()
  1065.         
  1066.  
  1067.     
  1068.     def stopDownloadingCurrentItems(self):
  1069.         selected = self.selection.getSelectedItems()
  1070.         downloading = _[1]
  1071.         for item in downloading:
  1072.             item.expire()
  1073.         
  1074.  
  1075.     
  1076.     def pauseDownloadingCurrentItems(self):
  1077.         selected = self.selection.getSelectedItems()
  1078.         downloading = _[1]
  1079.         for item in downloading:
  1080.             item.pause()
  1081.         
  1082.  
  1083.     
  1084.     def updateCurrentFeed(self):
  1085.         for tab in self.selection.getSelectedTabs():
  1086.             if tab.isFeed():
  1087.                 tab.obj.update()
  1088.                 continue
  1089.         
  1090.  
  1091.     
  1092.     def updateAllFeeds(self):
  1093.         for f in views.feeds:
  1094.             f.update()
  1095.         
  1096.  
  1097.     
  1098.     def removeGuide(self, guide):
  1099.         if guide.getDefault():
  1100.             logging.warning('attempt to remove default guide')
  1101.             return None
  1102.         
  1103.         title = _('Remove %s') % guide.getTitle()
  1104.         description = _('Are you sure you want to remove the guide %s?') % (guide.getTitle(),)
  1105.         dialog = dialogs.ChoiceDialog(title, description, dialogs.BUTTON_YES, dialogs.BUTTON_NO)
  1106.         
  1107.         def dialogCallback(dialog):
  1108.             if guide.idExists() and dialog.choice == dialogs.BUTTON_YES:
  1109.                 guide.remove()
  1110.             
  1111.  
  1112.         dialog.run(dialogCallback)
  1113.  
  1114.     
  1115.     def removePlaylist(self, playlist):
  1116.         return self.removePlaylists([
  1117.             playlist])
  1118.  
  1119.     
  1120.     def removePlaylists(self, playlists):
  1121.         if len(playlists) == 1:
  1122.             title = _('Remove %s') % playlists[0].getTitle()
  1123.             description = _('Are you sure you want to remove %s') % playlists[0].getTitle()
  1124.         else:
  1125.             title = _('Remove %s channels') % len(playlists)
  1126.             description = _('Are you sure you want to remove these %s playlists') % len(playlists)
  1127.         dialog = dialogs.ChoiceDialog(title, description, dialogs.BUTTON_YES, dialogs.BUTTON_NO)
  1128.         
  1129.         def dialogCallback(dialog):
  1130.             if dialog.choice == dialogs.BUTTON_YES:
  1131.                 for playlist in playlists:
  1132.                     if playlist.idExists():
  1133.                         playlist.remove()
  1134.                         continue
  1135.                 
  1136.             
  1137.  
  1138.         dialog.run(dialogCallback)
  1139.  
  1140.     
  1141.     def removeFeed(self, feed):
  1142.         return self.removeFeeds([
  1143.             feed])
  1144.  
  1145.     
  1146.     def removeFeeds(self, feeds):
  1147.         downloads = False
  1148.         downloading = False
  1149.         allDirectories = True
  1150.         for feed in feeds:
  1151.             if isinstance(feed, folder.ChannelFolder) or not feed.getURL().startswith('dtv:directoryfeed'):
  1152.                 allDirectories = False
  1153.                 if feed.hasDownloadedItems():
  1154.                     downloads = True
  1155.                     break
  1156.                 
  1157.                 if feed.hasDownloadingItems():
  1158.                     downloading = True
  1159.                 
  1160.             feed.hasDownloadingItems()
  1161.         
  1162.         if downloads:
  1163.             self.removeFeedsWithDownloads(feeds)
  1164.         elif downloading:
  1165.             self.removeFeedsWithDownloading(feeds)
  1166.         elif allDirectories:
  1167.             self.removeDirectoryFeeds(feeds)
  1168.         else:
  1169.             self.removeFeedsNormal(feeds)
  1170.  
  1171.     
  1172.     def removeFeedsWithDownloads(self, feeds):
  1173.         if len(feeds) == 1:
  1174.             title = _('Remove %s') % feeds[0].getTitle()
  1175.             description = _("What would you like to do with the videos in this channel that you've downloaded?")
  1176.         else:
  1177.             title = _('Remove %s channels') % len(feeds)
  1178.             description = _("What would you like to do with the videos in these channels that you've downloaded?")
  1179.         dialog = dialogs.ThreeChoiceDialog(title, description, dialogs.BUTTON_KEEP_VIDEOS, dialogs.BUTTON_DELETE_VIDEOS, dialogs.BUTTON_CANCEL)
  1180.         
  1181.         def dialogCallback(dialog):
  1182.             if dialog.choice == dialogs.BUTTON_KEEP_VIDEOS:
  1183.                 manualFeed = util.getSingletonDDBObject(views.manualFeed)
  1184.                 for feed in feeds:
  1185.                     if feed.idExists():
  1186.                         feed.remove(moveItemsTo = manualFeed)
  1187.                         continue
  1188.                 
  1189.             elif dialog.choice == dialogs.BUTTON_DELETE_VIDEOS:
  1190.                 for feed in feeds:
  1191.                     if feed.idExists():
  1192.                         feed.remove()
  1193.                         continue
  1194.                 
  1195.             
  1196.  
  1197.         dialog.run(dialogCallback)
  1198.  
  1199.     
  1200.     def removeFeedsWithDownloading(self, feeds):
  1201.         if len(feeds) == 1:
  1202.             title = _('Remove %s') % feeds[0].getTitle()
  1203.             description = _('Are you sure you want to remove %s?  Any downloads in progress will be canceled.') % feeds[0].getTitle()
  1204.         else:
  1205.             title = _('Remove %s channels') % len(feeds)
  1206.             description = _('Are you sure you want to remove these %s channels?  Any downloads in progress will be canceled.') % len(feeds)
  1207.         dialog = dialogs.ChoiceDialog(title, description, dialogs.BUTTON_YES, dialogs.BUTTON_NO)
  1208.         
  1209.         def dialogCallback(dialog):
  1210.             if dialog.choice == dialogs.BUTTON_YES:
  1211.                 for feed in feeds:
  1212.                     if feed.idExists():
  1213.                         feed.remove()
  1214.                         continue
  1215.                 
  1216.             
  1217.  
  1218.         dialog.run(dialogCallback)
  1219.  
  1220.     
  1221.     def removeFeedsNormal(self, feeds):
  1222.         if len(feeds) == 1:
  1223.             title = _('Remove %s') % feeds[0].getTitle()
  1224.             description = _('Are you sure you want to remove %s?') % feeds[0].getTitle()
  1225.         else:
  1226.             title = _('Remove %s channels') % len(feeds)
  1227.             description = _('Are you sure you want to remove these %s channels?') % len(feeds)
  1228.         dialog = dialogs.ChoiceDialog(title, description, dialogs.BUTTON_YES, dialogs.BUTTON_NO)
  1229.         
  1230.         def dialogCallback(dialog):
  1231.             if dialog.choice == dialogs.BUTTON_YES:
  1232.                 for feed in feeds:
  1233.                     if feed.idExists():
  1234.                         feed.remove()
  1235.                         continue
  1236.                 
  1237.             
  1238.  
  1239.         dialog.run(dialogCallback)
  1240.  
  1241.     
  1242.     def removeDirectoryFeeds(self, feeds):
  1243.         if len(feeds) == 1:
  1244.             title = _('Stop watching %s') % feeds[0].getTitle()
  1245.             description = _('Are you sure you want to stop watching %s?') % feeds[0].getTitle()
  1246.         else:
  1247.             title = _('Stop watching %s directories') % len(feeds)
  1248.             description = _('Are you sure you want to stop watching these %s directories?') % len(feeds)
  1249.         dialog = dialogs.ChoiceDialog(title, description, dialogs.BUTTON_YES, dialogs.BUTTON_NO)
  1250.         
  1251.         def dialogCallback(dialog):
  1252.             if dialog.choice == dialogs.BUTTON_YES:
  1253.                 for feed in feeds:
  1254.                     if feed.idExists():
  1255.                         feed.remove()
  1256.                         continue
  1257.                 
  1258.             
  1259.  
  1260.         dialog.run(dialogCallback)
  1261.  
  1262.     
  1263.     def playView(self, view, firstItemId = None, justPlayOne = False):
  1264.         self.playbackController.configure(view, firstItemId, justPlayOne)
  1265.         self.playbackController.enterPlayback()
  1266.  
  1267.     
  1268.     def downloaderShutdown(self):
  1269.         logging.info('Closing Database...')
  1270.         database.defaultDatabase.liveStorage.close()
  1271.         logging.info('Shutting down event loop')
  1272.         eventloop.quit()
  1273.         logging.info('Shutting down frontend')
  1274.         frontend.quit()
  1275.  
  1276.     
  1277.     def quit(self):
  1278.         if self.inQuit:
  1279.             return None
  1280.         
  1281.         downloadsCount = views.downloadingItems.len()
  1282.         if downloadsCount > 0 or config.get(prefs.WARN_IF_DOWNLOADING_ON_QUIT) or self.sendingCrashReport > 0:
  1283.             title = _('Are you sure you want to quit?')
  1284.             if self.sendingCrashReport > 0:
  1285.                 message = _('Miro is still uploading your crash report. If you quit now the upload will be canceled.  Quit Anyway?')
  1286.                 dialog = dialogs.ChoiceDialog(title, message, dialogs.BUTTON_QUIT, dialogs.BUTTON_CANCEL)
  1287.             else:
  1288.                 message = ngettext('You have %d download still in progress.  Quit Anyway?', 'You have %d downloads still in progress.  Quit Anyway?', downloadsCount) % (downloadsCount,)
  1289.                 warning = _('Warn me when I attempt to quit with downloads in progress')
  1290.                 dialog = dialogs.CheckboxDialog(title, message, warning, True, dialogs.BUTTON_QUIT, dialogs.BUTTON_CANCEL)
  1291.             
  1292.             def callback(dialog):
  1293.                 if dialog.choice == dialogs.BUTTON_QUIT:
  1294.                     if isinstance(dialog, dialogs.CheckboxDialog):
  1295.                         config.set(prefs.WARN_IF_DOWNLOADING_ON_QUIT, dialog.checkbox_value)
  1296.                     
  1297.                     self.quitStage2()
  1298.                 else:
  1299.                     self.inQuit = False
  1300.  
  1301.             dialog.run(callback)
  1302.             self.inQuit = True
  1303.         else:
  1304.             self.quitStage2()
  1305.  
  1306.     quit = eventloop.asUrgent(quit)
  1307.     
  1308.     def quitStage2(self):
  1309.         logging.info('Shutting down Downloader...')
  1310.         downloader.shutdownDownloader(self.downloaderShutdown)
  1311.  
  1312.     
  1313.     def setGuideURL(self, guideURL):
  1314.         '''Change the URL of the current channel guide being displayed.  If no
  1315.         guide is being display, pass in None.
  1316.  
  1317.         This method must be called from the onSelectedTabChange in the
  1318.         platform code.  URLs are legal within guideURL will be allow
  1319.         through in onURLLoad().
  1320.         '''
  1321.         self.guide = None
  1322.         if guideURL is not None:
  1323.             self.guideURL = guideURL
  1324.             for guideObj in views.guides:
  1325.                 if guideObj.getURL() == controller.guideURL:
  1326.                     self.guide = guideObj
  1327.                     continue
  1328.             
  1329.         else:
  1330.             self.guideURL = None
  1331.  
  1332.     setGuideURL = eventloop.asUrgent(setGuideURL)
  1333.     
  1334.     def setLastVisitedGuideURL(self, url):
  1335.         selectedTabs = self.selection.getSelectedTabs()
  1336.         selectedObjects = [ t.obj for t in selectedTabs ]
  1337.         if selectedObjects[0].isPartOfGuide(url):
  1338.             if url.startswith(u'http://') or url.startswith(u'https://'):
  1339.                 selectedObjects[0].lastVisitedURL = url
  1340.             else:
  1341.                 logging.warn('setLastVisitedGuideURL called, but the guide is no longer selected')
  1342.  
  1343.     setLastVisitedGuideURL = eventloop.asIdle(setLastVisitedGuideURL)
  1344.     
  1345.     def onShutdown(self):
  1346.         
  1347.         try:
  1348.             eventloop.join()
  1349.             logging.info('Saving preferences...')
  1350.             config.save()
  1351.             logging.info('Shutting down icon cache updates')
  1352.             iconcache.iconCacheUpdater.shutdown()
  1353.             logging.info('Shutting down movie data updates')
  1354.             moviedata.movieDataUpdater.shutdown()
  1355.             if self.idlingNotifier is not None:
  1356.                 logging.info('Shutting down IdleNotifier')
  1357.                 self.idlingNotifier.join()
  1358.             
  1359.             logging.info('Done shutting down.')
  1360.             logging.info('Remaining threads are:')
  1361.             for thread in threading.enumerate():
  1362.                 logging.info('%s', thread)
  1363.         except:
  1364.             util.failedExn('while shutting down')
  1365.             frontend.exit(1)
  1366.  
  1367.  
  1368.     
  1369.     def configDidChange(self, key, value):
  1370.         if key is prefs.LIMIT_UPSTREAM.key:
  1371.             if value is False:
  1372.                 
  1373.                 try:
  1374.                     self.idlingNotifier.join()
  1375.                 except:
  1376.                     pass
  1377.  
  1378.                 self.idlingNotifier = None
  1379.             elif self.idlingNotifier is None:
  1380.                 self.idlingNotifier = idlenotifier.IdleNotifier(self)
  1381.                 self.idlingNotifier.start()
  1382.             
  1383.         
  1384.  
  1385.     
  1386.     def systemHasBeenIdlingSince(self, seconds):
  1387.         self.setUpstreamLimit(False)
  1388.  
  1389.     
  1390.     def systemIsActiveAgain(self):
  1391.         self.setUpstreamLimit(True)
  1392.  
  1393.     
  1394.     def addAndSelectFeed(self, url = None, showTemplate = None):
  1395.         return GUIActionHandler().addFeed(url, showTemplate)
  1396.  
  1397.     
  1398.     def addAndSelectGuide(self, url = None):
  1399.         return GUIActionHandler().addGuide(url)
  1400.  
  1401.     
  1402.     def addSearchFeed(self, term = None, style = dialogs.SearchChannelDialog.CHANNEL, location = None):
  1403.         return GUIActionHandler().addSearchFeed(term, style, location)
  1404.  
  1405.     
  1406.     def testSearchFeedDialog(self):
  1407.         return GUIActionHandler().testSearchFeedDialog()
  1408.  
  1409.     
  1410.     def addFeed(self, url = None):
  1411.         return GUIActionHandler().addFeed(url, selected = None)
  1412.  
  1413.     
  1414.     def selectFeed(self, url):
  1415.         return GUIActionHandler().selectFeed(url)
  1416.  
  1417.     
  1418.     def onUnwatchedItemsCountChange(self, obj, id):
  1419.         if not self.newTab is not None:
  1420.             raise AssertionError
  1421.         self.newTab.redraw()
  1422.         self.updateAvailableItemsCountFeedback()
  1423.         if hasattr(frontend.Application, 'onUnwatchedItemsCountChange'):
  1424.             frontend.Application.onUnwatchedItemsCountChange(self, obj, id)
  1425.         
  1426.  
  1427.     
  1428.     def onDownloadingItemsCountChange(self, obj, id):
  1429.         if not self.downloadTab is not None:
  1430.             raise AssertionError
  1431.         self.downloadTab.redraw()
  1432.         if hasattr(frontend.Application, 'onDownloadingItemsCountChange'):
  1433.             frontend.Application.onDownloadingItemsCountChange(self, obj, id)
  1434.         
  1435.  
  1436.     
  1437.     def updateAvailableItemsCountFeedback(self):
  1438.         count = views.unwatchedItems.len()
  1439.         delegate.updateAvailableItemsCountFeedback(count)
  1440.  
  1441.     
  1442.     def performSearch(self, engine, query):
  1443.         util.checkU(engine)
  1444.         util.checkU(query)
  1445.         handler = TemplateActionHandler(None, None)
  1446.         handler.updateLastSearchEngine(engine)
  1447.         handler.updateLastSearchQuery(query)
  1448.         handler.performSearch(engine, query)
  1449.         self.selection.selectTabByTemplateBase('searchtab')
  1450.  
  1451.     
  1452.     def setUpstreamLimit(self, setLimit):
  1453.         if setLimit:
  1454.             limit = config.get(prefs.UPSTREAM_LIMIT_IN_KBS)
  1455.         
  1456.  
  1457.     
  1458.     def handleURIDrop(self, data, **kwargs):
  1459.         '''Handle an external drag that contains a text/uri-list mime-type.
  1460.         data should be the text/uri-list data, in escaped form.
  1461.  
  1462.         kwargs is thrown away.  It exists to catch weird URLs, like
  1463.         javascript: which sometime result in us getting extra arguments.
  1464.         '''
  1465.         lastAddedFeed = None
  1466.         data = urllib.unquote(data)
  1467.         for url in data.split(u'\n'):
  1468.             url = url.strip()
  1469.             if url == u'':
  1470.                 continue
  1471.             
  1472.             if url.startswith(u'file://'):
  1473.                 filename = download_utils.getFileURLPath(url)
  1474.                 filename = platformutils.osFilenameToFilenameType(filename)
  1475.                 eventloop.addIdle(singleclick.openFile, 'Open Dropped file', args = (filename,))
  1476.                 continue
  1477.             if url.startswith(u'http:') or url.startswith(u'https:'):
  1478.                 url = feed.normalizeFeedURL(url)
  1479.                 if feed.validateFeedURL(url) and not feed.getFeedByURL(url):
  1480.                     lastAddedFeed = feed.Feed(url)
  1481.                 
  1482.             not feed.getFeedByURL(url)
  1483.         
  1484.         if lastAddedFeed:
  1485.             controller.selection.selectTabByObject(lastAddedFeed)
  1486.         
  1487.  
  1488.     
  1489.     def handleDrop(self, dropData, type, sourceData):
  1490.         
  1491.         try:
  1492.             (destType, destID) = dropData.split('-')
  1493.             if destID == 'END':
  1494.                 destObj = None
  1495.             elif destID == 'START':
  1496.                 if destType == 'channel':
  1497.                     tabOrder = util.getSingletonDDBObject(views.channelTabOrder)
  1498.                 else:
  1499.                     tabOrder = util.getSingletonDDBObject(views.playlistTabOrder)
  1500.                 for tab in tabOrder.getView():
  1501.                     destObj = tab.obj
  1502.                 
  1503.             else:
  1504.                 destObj = db.getObjectByID(int(destID))
  1505.             (sourceArea, sourceID) = sourceData.split('-')
  1506.             sourceID = int(sourceID)
  1507.             draggedIDs = self.selection.calcSelection(sourceArea, sourceID)
  1508.         except:
  1509.             logging.exception('error parsing drop (%r, %r, %r)', dropData, type, sourceData)
  1510.             return None
  1511.  
  1512.         if destType == 'playlist' and type == 'downloadeditem':
  1513.             destObj.handleDNDAppend(draggedIDs)
  1514.         elif (destType == 'channelfolder' or type == 'channel' or destType == 'playlistfolder') and type == 'playlist':
  1515.             obj = db.getObjectByID(int(destID))
  1516.             obj.handleDNDAppend(draggedIDs)
  1517.         elif destType in ('playlist', 'playlistfolder') and type in ('playlist', 'playlistfolder'):
  1518.             tabOrder = util.getSingletonDDBObject(views.playlistTabOrder)
  1519.             tabOrder.handleDNDReorder(destObj, draggedIDs)
  1520.         elif destType in ('channel', 'channelfolder') and type in ('channel', 'channelfolder'):
  1521.             tabOrder = util.getSingletonDDBObject(views.channelTabOrder)
  1522.             tabOrder.handleDNDReorder(destObj, draggedIDs)
  1523.         elif destType == 'playlistitem' and type == 'downloadeditem':
  1524.             playlist = self.selection.getSelectedTabs()[0].obj
  1525.             playlist.handleDNDReorder(destObj, draggedIDs)
  1526.         else:
  1527.             logging.info("Can't handle drop. Dest type: %s Dest id: %s Type: %s", destType, destID, type)
  1528.  
  1529.     
  1530.     def addToNewPlaylist(self):
  1531.         selected = controller.selection.getSelectedItems()
  1532.         childIDs = _[1]
  1533.         playlist.createNewPlaylist(childIDs)
  1534.  
  1535.     
  1536.     def startUploads(self):
  1537.         selected = controller.selection.getSelectedItems()
  1538.         for i in selected:
  1539.             i.startUpload()
  1540.         
  1541.  
  1542.     
  1543.     def newDownload(self, url = None):
  1544.         return GUIActionHandler().addDownload(url)
  1545.  
  1546.     
  1547.     def importChannels(self):
  1548.         importer = opml.Importer()
  1549.         importer.importSubscriptions()
  1550.  
  1551.     
  1552.     def exportChannels(self):
  1553.         exporter = opml.Exporter()
  1554.         exporter.exportSubscriptions()
  1555.  
  1556.  
  1557.  
  1558. class TemplateDisplay(frontend.HTMLDisplay):
  1559.     
  1560.     def __init__(self, templateName, templateState, frameHint = None, areaHint = None, baseURL = None, *args, **kargs):
  1561.         """'templateName' is the name of the inital template file.  'data' is
  1562.         keys for the template. 'templateState' is a string with the state of the
  1563.         template.
  1564.         """
  1565.         logging.debug('Processing %s', templateName)
  1566.         self.templateName = templateName
  1567.         self.templateState = templateState
  1568.         (tch, self.templateHandle) = template.fillTemplate(templateName, self, self.getDTVPlatformName(), self.getEventCookie(), self.getBodyTagExtra(), templateState = templateState, *args, **kargs)
  1569.         self.args = args
  1570.         self.kargs = kargs
  1571.         html = tch.read()
  1572.         self.actionHandlers = [
  1573.             ModelActionHandler(delegate),
  1574.             GUIActionHandler(),
  1575.             TemplateActionHandler(self, self.templateHandle)]
  1576.         loadTriggers = self.templateHandle.getTriggerActionURLsOnLoad()
  1577.         newPage = self.runActionURLs(loadTriggers)
  1578.         if newPage:
  1579.             self.templateHandle.unlinkTemplate()
  1580.             self.__init__(re.compile('^template:(.*)$').match(url).group(1), frameHint, areaHint, baseURL)
  1581.         else:
  1582.             frontend.HTMLDisplay.__init__(self, html, frameHint = frameHint, areaHint = areaHint, baseURL = baseURL)
  1583.             self.templateHandle.initialFillIn()
  1584.  
  1585.     
  1586.     def __eq__(self, other):
  1587.         if other.__class__ == TemplateDisplay and self.templateName == other.templateName and self.args == other.args:
  1588.             pass
  1589.         return self.kargs == other.kargs
  1590.  
  1591.     
  1592.     def __str__(self):
  1593.         return 'Template <%s> args=%s kargs=%s' % (self.templateName, self.args, self.kargs)
  1594.  
  1595.     
  1596.     def reInit(self, *args, **kargs):
  1597.         self.args = args
  1598.         self.kargs = kargs
  1599.         
  1600.         try:
  1601.             self.templateHandle.templateVars['reInit'](*args, **kargs)
  1602.         except:
  1603.             pass
  1604.  
  1605.         self.templateHandle.forceUpdate()
  1606.  
  1607.     
  1608.     def runActionURLs(self, triggers):
  1609.         newPage = False
  1610.         for url in triggers:
  1611.             if url.startswith('action:'):
  1612.                 self.onURLLoad(url)
  1613.                 continue
  1614.             if url.startswith('template:'):
  1615.                 newPage = True
  1616.                 break
  1617.                 continue
  1618.         
  1619.         return newPage
  1620.  
  1621.     
  1622.     def parseEventURL(self, url):
  1623.         match = re.match('[a-zA-Z]+:([^?]+)(\\?(.*))?$', url)
  1624.         if match:
  1625.             path = match.group(1)
  1626.             argString = match.group(3)
  1627.             if argString is None:
  1628.                 argString = u''
  1629.             
  1630.             argString = argString.encode('utf8')
  1631.             argLists = cgi.parse_qs(argString, keep_blank_values = True)
  1632.             args = { }
  1633.             for key in argLists.keys():
  1634.                 value = argLists[key]
  1635.                 if len(value) != 1:
  1636.                     import template_compiler
  1637.                     raise template_compiler.TemplateError, "Multiple values of '%s' argument passed to '%s' action" % (key, url)
  1638.                 
  1639.                 
  1640.                 try:
  1641.                     args[key.encode('ascii', 'replace')] = value[0].decode('utf8')
  1642.                 continue
  1643.                 args[key.encode('ascii', 'replace')] = value[0].decode('ascii', 'replace')
  1644.                 continue
  1645.  
  1646.             
  1647.             return (path, args)
  1648.         else:
  1649.             raise ValueError('Badly formed eventURL: %s' % url)
  1650.  
  1651.     
  1652.     def onURLLoad(self, url):
  1653.         util.checkU(url)
  1654.         logging.info('got %s', url)
  1655.         
  1656.         try:
  1657.             if url.startswith(u'template:'):
  1658.                 (name, args) = self.parseEventURL(url)
  1659.                 self.dispatchAction('switchTemplate', name = name, **args)
  1660.                 return False
  1661.             
  1662.             if url.startswith(u'action:'):
  1663.                 (action, args) = self.parseEventURL(url)
  1664.                 self.dispatchAction(action, **args)
  1665.                 return False
  1666.             
  1667.             if controller.guide is not None and controller.guide.isPartOfGuide(url):
  1668.                 controller.setLastVisitedGuideURL(url)
  1669.                 return True
  1670.             
  1671.             if url.startswith(u'file://'):
  1672.                 path = download_utils.getFileURLPath(url)
  1673.                 return os.path.exists(path)
  1674.             
  1675.             if url.startswith(u'http://') and url.startswith(u'https://') and url.startswith(u'ftp://') and url.startswith(u'mailto:') or url.startswith(u'feed://'):
  1676.                 self.handleCandidateExternalURL(url)
  1677.                 return False
  1678.         except:
  1679.             details = "Handling action URL '%s'" % (url,)
  1680.             util.failedExn('while handling a request', details = details)
  1681.  
  1682.         return True
  1683.  
  1684.     
  1685.     def handleCandidateExternalURL(self, url):
  1686.         """Open a URL that onURLLoad thinks is an external URL.
  1687.         handleCandidateExternalURL does extra checks that onURLLoad can't do
  1688.         because it's happens in the gui thread and can't access the DB.
  1689.         """
  1690.         (type, subscribeURLs) = subscription.findSubscribeLinks(url)
  1691.         if len(subscribeURLs) == 0:
  1692.             for guideObj in views.guides:
  1693.                 if guideObj.isPartOfGuide(url):
  1694.                     return None
  1695.                     continue
  1696.             
  1697.         
  1698.         normalizedURLs = []
  1699.         for url in subscribeURLs:
  1700.             normalized = feed.normalizeFeedURL(url)
  1701.             if feed.validateFeedURL(normalized):
  1702.                 normalizedURLs.append(normalized)
  1703.                 continue
  1704.         
  1705.         if normalizedURLs:
  1706.             if type == 'feed':
  1707.                 for url in normalizedURLs:
  1708.                     if feed.getFeedByURL(url) is None:
  1709.                         newFeed = feed.Feed(url)
  1710.                         newFeed.blink()
  1711.                         continue
  1712.                 
  1713.             elif type == 'download':
  1714.                 for url in normalizedURLs:
  1715.                     filename = platformutils.unicodeToFilename(url)
  1716.                     singleclick.downloadURL(filename)
  1717.                 
  1718.             elif type == 'guide':
  1719.                 for url in normalizedURLs:
  1720.                     if guide.getGuideByURL(url) is None:
  1721.                         guide.ChannelGuide(url)
  1722.                         continue
  1723.                 
  1724.             else:
  1725.                 raise AssertionError('Unkown subscribe type')
  1726.             return None
  1727.         
  1728.         if url.startswith(u'feed://'):
  1729.             url = u'http://' + url[len(u'feed://'):]
  1730.             f = feed.getFeedByURL(url)
  1731.             if f is None:
  1732.                 f = feed.Feed(url)
  1733.             
  1734.             f.blink()
  1735.             return None
  1736.         
  1737.         delegate.openExternalURL(url)
  1738.  
  1739.     handleCandidateExternalURL = eventloop.asUrgent(handleCandidateExternalURL)
  1740.     
  1741.     def dispatchAction(self, action, **kwargs):
  1742.         called = False
  1743.         start = clock()
  1744.         for handler in self.actionHandlers:
  1745.             if hasattr(handler, action):
  1746.                 getattr(handler, action)(**kwargs)
  1747.                 called = True
  1748.                 break
  1749.                 continue
  1750.         
  1751.         end = clock()
  1752.         if end - start > 0.5:
  1753.             logging.timing('dispatch action %s too slow (%.3f secs)', action, end - start)
  1754.         
  1755.         if not called:
  1756.             logging.warning('Ignored bad action URL: action=%s', action)
  1757.         
  1758.  
  1759.     dispatchAction = eventloop.asUrgent(dispatchAction)
  1760.     
  1761.     def onDeselected(self, frame):
  1762.         unloadTriggers = self.templateHandle.getTriggerActionURLsOnUnload()
  1763.         self.runActionURLs(unloadTriggers)
  1764.         self.unlink()
  1765.         frontend.HTMLDisplay.onDeselected(self, frame)
  1766.  
  1767.     onDeselected = eventloop.asUrgent(onDeselected)
  1768.     
  1769.     def unlink(self):
  1770.         self.templateHandle.unlinkTemplate()
  1771.         self.actionHandlers = []
  1772.  
  1773.  
  1774.  
  1775. class ModelActionHandler:
  1776.     
  1777.     def __init__(self, backEndDelegate):
  1778.         self.backEndDelegate = backEndDelegate
  1779.  
  1780.     
  1781.     def setAutoDownloadMode(self, feed, mode):
  1782.         obj = db.getObjectByID(int(feed))
  1783.         obj.setAutoDownloadMode(mode)
  1784.  
  1785.     
  1786.     def setExpiration(self, feed, type, time):
  1787.         obj = db.getObjectByID(int(feed))
  1788.         obj.setExpiration(type, int(time))
  1789.  
  1790.     
  1791.     def setMaxNew(self, feed, maxNew):
  1792.         obj = db.getObjectByID(int(feed))
  1793.         obj.setMaxNew(int(maxNew))
  1794.  
  1795.     
  1796.     def invalidMaxNew(self, value):
  1797.         title = _('Invalid Value')
  1798.         description = _('%s is invalid.  You must enter a non-negative number.') % value
  1799.         dialogs.MessageBoxDialog(title, description).run()
  1800.  
  1801.     
  1802.     def startDownload(self, item):
  1803.         
  1804.         try:
  1805.             obj = db.getObjectByID(int(item))
  1806.             obj.download()
  1807.         except database.ObjectNotFoundError:
  1808.             pass
  1809.  
  1810.  
  1811.     
  1812.     def removeFeed(self, id):
  1813.         
  1814.         try:
  1815.             feed = db.getObjectByID(int(id))
  1816.             controller.removeFeed(feed)
  1817.         except database.ObjectNotFoundError:
  1818.             pass
  1819.  
  1820.  
  1821.     
  1822.     def removeCurrentFeed(self):
  1823.         controller.removeCurrentFeed()
  1824.  
  1825.     
  1826.     def removeCurrentPlaylist(self):
  1827.         controller.removeCurrentPlaylist()
  1828.  
  1829.     
  1830.     def removeCurrentItems(self):
  1831.         controller.removeCurrentItems()
  1832.  
  1833.     
  1834.     def mergeToFolder(self):
  1835.         tls = controller.selection.tabListSelection
  1836.         selectionType = tls.getType()
  1837.         childIDs = set(tls.currentSelection)
  1838.         if selectionType == 'channeltab':
  1839.             folder.createNewChannelFolder(childIDs)
  1840.         elif selectionType == 'playlisttab':
  1841.             folder.createNewPlaylistFolder(childIDs)
  1842.         else:
  1843.             logging.warning('bad selection type %s in mergeToFolder', selectionType)
  1844.  
  1845.     
  1846.     def remove(self, area, id):
  1847.         selectedIDs = controller.selection.calcSelection(area, int(id))
  1848.         selectedObjects = [ db.getObjectByID(id) for id in selectedIDs ]
  1849.         objType = selectedObjects[0].__class__
  1850.         if objType in (feed.Feed, folder.ChannelFolder):
  1851.             controller.removeFeeds(selectedObjects)
  1852.         elif objType in (playlist.SavedPlaylist, folder.PlaylistFolder):
  1853.             controller.removePlaylists(selectedObjects)
  1854.         elif objType == guide.ChannelGuide:
  1855.             if len(selectedObjects) != 1:
  1856.                 raise AssertionError('Multiple guides selected in remove')
  1857.             
  1858.             controller.removeGuide(selectedObjects[0])
  1859.         elif objType == item.Item:
  1860.             pl = controller.selection.getSelectedTabs()[0].obj
  1861.             pl.handleRemove(destObj, selectedIDs)
  1862.         else:
  1863.             logging.warning("Can't handle type %s in remove()", objType)
  1864.  
  1865.     
  1866.     def rename(self, id):
  1867.         
  1868.         try:
  1869.             obj = db.getObjectByID(int(id))
  1870.         except:
  1871.             logging.warning("tried to rename object that doesn't exist with id %d", int(feed))
  1872.             return None
  1873.  
  1874.         if obj.__class__ in (playlist.SavedPlaylist, folder.ChannelFolder, folder.PlaylistFolder):
  1875.             obj.rename()
  1876.         else:
  1877.             logging.warning('Unknown object type in remove() %s', type(obj))
  1878.  
  1879.     
  1880.     def updateFeed(self, feed):
  1881.         obj = db.getObjectByID(int(feed))
  1882.         obj.update()
  1883.  
  1884.     
  1885.     def copyFeedURL(self, feed):
  1886.         obj = db.getObjectByID(int(feed))
  1887.         url = obj.getURL()
  1888.         self.backEndDelegate.copyTextToClipboard(url)
  1889.  
  1890.     
  1891.     def markFeedViewed(self, feed):
  1892.         
  1893.         try:
  1894.             obj = db.getObjectByID(int(feed))
  1895.             obj.markAsViewed()
  1896.         except database.ObjectNotFoundError:
  1897.             pass
  1898.  
  1899.  
  1900.     
  1901.     def updateIcons(self, feed):
  1902.         
  1903.         try:
  1904.             obj = db.getObjectByID(int(feed))
  1905.             obj.updateIcons()
  1906.         except database.ObjectNotFoundError:
  1907.             pass
  1908.  
  1909.  
  1910.     
  1911.     def expireItem(self, item):
  1912.         
  1913.         try:
  1914.             obj = db.getObjectByID(int(item))
  1915.             obj.expire()
  1916.         except database.ObjectNotFoundError:
  1917.             logging.warning("tried to expire item that doesn't exist with id %d", int(item))
  1918.  
  1919.  
  1920.     
  1921.     def expirePlayingItem(self, item):
  1922.         self.expireItem(item)
  1923.         controller.playbackController.skip(1)
  1924.  
  1925.     
  1926.     def addItemToLibrary(self, item):
  1927.         obj = db.getObjectByID(int(item))
  1928.         manualFeed = util.getSingletonDDBObject(views.manualFeed)
  1929.         obj.setFeed(manualFeed.getID())
  1930.  
  1931.     
  1932.     def keepItem(self, item):
  1933.         obj = db.getObjectByID(int(item))
  1934.         obj.save()
  1935.  
  1936.     
  1937.     def stopUploadItem(self, item):
  1938.         obj = db.getObjectByID(int(item))
  1939.         obj.stopUpload()
  1940.  
  1941.     
  1942.     def toggleMoreItemInfo(self, item):
  1943.         obj = db.getObjectByID(int(item))
  1944.         obj.toggleShowMoreInfo()
  1945.  
  1946.     
  1947.     def revealItem(self, item):
  1948.         obj = db.getObjectByID(int(item))
  1949.         filename = obj.getFilename()
  1950.         if not os.path.exists(filename):
  1951.             basename = os.path.basename(filename)
  1952.             title = _('Error Revealing File')
  1953.             msg = _('The file "%s" was deleted from outside Miro.') % basename
  1954.             dialogs.MessageBoxDialog(title, msg).run()
  1955.         else:
  1956.             self.backEndDelegate.revealFile(filename)
  1957.  
  1958.     
  1959.     def clearTorrents(self):
  1960.         items = views.items.filter((lambda x: if x.getFeed().url == u'dtv:manualFeed' and x.isNonVideoFile():
  1961. passnot (x.getState() == u'downloading')))
  1962.         for i in items:
  1963.             if i.downloader is not None:
  1964.                 i.downloader.setDeleteFiles(False)
  1965.             
  1966.             i.remove()
  1967.         
  1968.  
  1969.     
  1970.     def pauseDownload(self, item):
  1971.         obj = db.getObjectByID(int(item))
  1972.         obj.pause()
  1973.  
  1974.     
  1975.     def resumeDownload(self, item):
  1976.         obj = db.getObjectByID(int(item))
  1977.         obj.resume()
  1978.  
  1979.     
  1980.     def pauseAll(self):
  1981.         autodler.pauseDownloader()
  1982.         for item in views.downloadingItems:
  1983.             item.pause()
  1984.         
  1985.  
  1986.     
  1987.     def resumeAll(self):
  1988.         for item in views.pausedItems:
  1989.             item.resume()
  1990.         
  1991.         autodler.resumeDownloader()
  1992.  
  1993.     
  1994.     def toggleExpand(self, id):
  1995.         obj = db.getObjectByID(int(id))
  1996.         obj.setExpanded(not obj.getExpanded())
  1997.  
  1998.     
  1999.     def setRunAtStartup(self, value):
  2000.         value = value == '1'
  2001.         self.backEndDelegate.setRunAtStartup(value)
  2002.  
  2003.     
  2004.     def setCheckEvery(self, value):
  2005.         value = int(value)
  2006.         config.set(prefs.CHECK_CHANNELS_EVERY_X_MN, value)
  2007.  
  2008.     
  2009.     def setLimitUpstream(self, value):
  2010.         value = value == '1'
  2011.         config.set(prefs.LIMIT_UPSTREAM, value)
  2012.  
  2013.     
  2014.     def setMaxUpstream(self, value):
  2015.         value = int(value)
  2016.         config.set(prefs.UPSTREAM_LIMIT_IN_KBS, value)
  2017.  
  2018.     
  2019.     def setPreserveDiskSpace(self, value):
  2020.         value = value == '1'
  2021.         config.set(prefs.PRESERVE_DISK_SPACE, value)
  2022.  
  2023.     
  2024.     def setDefaultExpiration(self, value):
  2025.         value = int(value)
  2026.         config.set(prefs.EXPIRE_AFTER_X_DAYS, value)
  2027.  
  2028.     
  2029.     def videoBombExternally(self, item):
  2030.         obj = db.getObjectByID(int(item))
  2031.         paramList = { }
  2032.         paramList['title'] = obj.getTitle()
  2033.         paramList['info_url'] = obj.getLink()
  2034.         paramList['hookup_url'] = obj.getPaymentLink()
  2035.         
  2036.         try:
  2037.             rss_url = obj.getFeed().getURL()
  2038.             if not rss_url.startswith(u'dtv:'):
  2039.                 paramList['rss_url'] = rss_url
  2040.         except:
  2041.             pass
  2042.  
  2043.         thumb_url = obj.getThumbnailURL()
  2044.         if thumb_url is not None:
  2045.             paramList['thumb_url'] = thumb_url
  2046.         
  2047.         paramString = ''
  2048.         glue = '?'
  2049.         url = obj.getURL()
  2050.         url.encode('utf-8', 'replace')
  2051.         if not url.startswith('file:'):
  2052.             paramString = '?url=%s' % xhtmltools.urlencode(url)
  2053.             glue = '&'
  2054.         
  2055.         for key in paramList.keys():
  2056.             if len(paramList[key]) > 0:
  2057.                 paramString = '%s%s%s=%s' % (paramString, glue, key, xhtmltools.urlencode(paramList[key]))
  2058.                 glue = '&'
  2059.                 continue
  2060.         
  2061.         description = obj.getDescription()
  2062.         if len(description) > 0:
  2063.             paramString = '%s%sdescription=%s' % (paramString, glue, xhtmltools.urlencode(description))
  2064.         
  2065.         url = config.get(prefs.VIDEOBOMB_URL) + paramString
  2066.         self.backEndDelegate.openExternalURL(url)
  2067.  
  2068.     
  2069.     def changeMoviesDirectory(self, newDir, migrate):
  2070.         changeMoviesDirectory(newDir, migrate == '1')
  2071.  
  2072.  
  2073.  
  2074. class printResultThread(threading.Thread):
  2075.     
  2076.     def __init__(self, format, func):
  2077.         self.format = format
  2078.         self.func = func
  2079.         threading.Thread.__init__(self)
  2080.  
  2081.     
  2082.     def run(self):
  2083.         print self.format % (self.func(),)
  2084.  
  2085.  
  2086.  
  2087. class GUIActionHandler:
  2088.     
  2089.     def playUnwatched(self):
  2090.         controller.playView(views.unwatchedItems)
  2091.  
  2092.     
  2093.     def openFile(self, path):
  2094.         singleclick.openFile(path)
  2095.  
  2096.     
  2097.     def addSearchFeed(self, term = None, style = dialogs.SearchChannelDialog.CHANNEL, location = None):
  2098.         
  2099.         def doAdd(dialog):
  2100.             if dialog.choice == dialogs.BUTTON_CREATE_CHANNEL:
  2101.                 self.addFeed(dialog.getURL())
  2102.             
  2103.  
  2104.         dialog = dialogs.SearchChannelDialog(term, style, location)
  2105.         if location == None:
  2106.             dialog.run(doAdd)
  2107.         else:
  2108.             self.addFeed(dialog.getURL())
  2109.  
  2110.     
  2111.     def addChannelSearchFeed(self, id):
  2112.         feed = db.getObjectByID(int(id))
  2113.         self.addSearchFeed(feed.inlineSearchTerm, dialogs.SearchChannelDialog.CHANNEL, int(id))
  2114.  
  2115.     
  2116.     def addEngineSearchFeed(self, term, name):
  2117.         self.addSearchFeed(term, dialogs.SearchChannelDialog.ENGINE, name)
  2118.  
  2119.     
  2120.     def testSearchFeedDialog(self):
  2121.         
  2122.         def finish(dialog):
  2123.             pass
  2124.  
  2125.         
  2126.         def thirdDialog(dialog):
  2127.             dialog = dialogs.SearchChannelDialog('Should select URL http://testurl/', dialogs.SearchChannelDialog.URL, 'http://testurl/')
  2128.             dialog.run(finish)
  2129.  
  2130.         
  2131.         def secondDialog(dialog):
  2132.             dialog = dialogs.SearchChannelDialog('Should select YouTube engine', dialogs.SearchChannelDialog.ENGINE, 'youtube')
  2133.             dialog.run(thirdDialog)
  2134.  
  2135.         dialog = dialogs.SearchChannelDialog('Should select third channel in list', dialogs.SearchChannelDialog.CHANNEL, -1)
  2136.         dialog.run(secondDialog)
  2137.  
  2138.     
  2139.     def addURL(self, title, message, callback, url = None):
  2140.         util.checkU(url)
  2141.         util.checkU(title)
  2142.         util.checkU(message)
  2143.         
  2144.         def createDialog(ltitle, lmessage, prefill = (None,)):
  2145.             
  2146.             def prefillCallback():
  2147.                 if prefill:
  2148.                     return prefill
  2149.                 else:
  2150.                     return None
  2151.  
  2152.             dialog = dialogs.TextEntryDialog(ltitle, lmessage, dialogs.BUTTON_OK, dialogs.BUTTON_CANCEL, prefillCallback, fillWithClipboardURL = prefill is None)
  2153.             
  2154.             def callback(dialog):
  2155.                 if dialog.choice == dialogs.BUTTON_OK:
  2156.                     doAdd(dialog.value)
  2157.                 
  2158.  
  2159.             dialog.run(callback)
  2160.  
  2161.         
  2162.         def doAdd(url):
  2163.             normalizedURL = feed.normalizeFeedURL(url)
  2164.             if not feed.validateFeedURL(normalizedURL):
  2165.                 ltitle = title + _(' - Invalid URL')
  2166.                 lmessage = _('The address you entered is not a valid URL.\nPlease double check and try again.\n\n') + message
  2167.                 createDialog(ltitle, lmessage, url)
  2168.                 return None
  2169.             
  2170.             callback(normalizedURL)
  2171.  
  2172.         if url is None:
  2173.             createDialog(title, message)
  2174.         else:
  2175.             doAdd(url)
  2176.  
  2177.     
  2178.     def addFeed(self, url = None, showTemplate = None, selected = '1'):
  2179.         if url:
  2180.             util.checkU(url)
  2181.         
  2182.         
  2183.         def doAdd(url):
  2184.             db.confirmDBThread()
  2185.             myFeed = feed.getFeedByURL(url)
  2186.             if myFeed is None:
  2187.                 myFeed = feed.Feed(url)
  2188.             
  2189.             if selected == '1':
  2190.                 controller.selection.selectTabByObject(myFeed)
  2191.             else:
  2192.                 myFeed.blink()
  2193.  
  2194.         self.addURL(Template(_('$shortAppName - Add Channel')).substitute(shortAppName = config.get(prefs.SHORT_APP_NAME)), _('Enter the URL of the channel to add'), doAdd, url)
  2195.  
  2196.     
  2197.     def selectFeed(self, url):
  2198.         url = feed.normalizeFeedURL(url)
  2199.         db.confirmDBThread()
  2200.         myFeed = feed.getFeedByURL(url)
  2201.         if myFeed is None:
  2202.             logging.warning('selectFeed: no such feed: %s', url)
  2203.             return None
  2204.         
  2205.         controller.selection.selectTabByObject(myFeed)
  2206.  
  2207.     
  2208.     def addGuide(self, url = None, selected = '1'):
  2209.         
  2210.         def doAdd(url):
  2211.             db.confirmDBThread()
  2212.             myGuide = guide.getGuideByURL(url)
  2213.             if myGuide is None:
  2214.                 myGuide = guide.ChannelGuide(url)
  2215.             
  2216.             if selected == '1':
  2217.                 controller.selection.selectTabByObject(myGuide)
  2218.             
  2219.  
  2220.         self.addURL(Template(_('$shortAppName - Add Miro Guide')).substitute(shortAppName = config.get(prefs.SHORT_APP_NAME)), _('Enter the URL of the Miro Guide to add'), doAdd, url)
  2221.  
  2222.     
  2223.     def addDownload(self, url = None):
  2224.         
  2225.         def doAdd(url):
  2226.             db.confirmDBThread()
  2227.             singleclick.downloadURL(platformutils.unicodeToFilename(url))
  2228.  
  2229.         self.addURL(Template(_('$shortAppName - Download Video')).substitute(shortAppName = config.get(prefs.SHORT_APP_NAME)), _('Enter the URL of the video to download'), doAdd, url)
  2230.  
  2231.     
  2232.     def handleDrop(self, data, type, sourcedata):
  2233.         controller.handleDrop(data, type, sourcedata)
  2234.  
  2235.     
  2236.     def handleURIDrop(self, data, **kwargs):
  2237.         controller.handleURIDrop(data, **kwargs)
  2238.  
  2239.     
  2240.     def showHelp(self):
  2241.         delegate.openExternalURL(config.get(prefs.HELP_URL))
  2242.  
  2243.     
  2244.     def reportBug(self):
  2245.         delegate.openExternalURL(config.get(prefs.BUG_REPORT_URL))
  2246.  
  2247.  
  2248.  
  2249. class TemplateActionHandler:
  2250.     
  2251.     def __init__(self, display, templateHandle):
  2252.         self.display = display
  2253.         self.templateHandle = templateHandle
  2254.         self.currentName = None
  2255.  
  2256.     
  2257.     def switchTemplate(self, name, state = 'default', baseURL = None, *args, **kargs):
  2258.         self.templateHandle.unlinkTemplate()
  2259.         template = TemplateDisplay(name, state, frameHint = controller.frame, areaHint = controller.frame.mainDisplay, baseURL = baseURL, *args, **kargs)
  2260.         controller.frame.selectDisplay(template, controller.frame.mainDisplay)
  2261.         self.currentName = name
  2262.  
  2263.     
  2264.     def setViewFilter(self, viewName, fieldKey, functionKey, parameter, invert):
  2265.         logging.warning('setViewFilter deprecated')
  2266.  
  2267.     
  2268.     def setViewSort(self, viewName, fieldKey, functionKey, reverse = 'false'):
  2269.         logging.warning('setViewSort deprecated')
  2270.  
  2271.     
  2272.     def setSearchString(self, searchString):
  2273.         
  2274.         try:
  2275.             self.templateHandle.getTemplateVariable('updateSearchString')(unicode(searchString))
  2276.         except KeyError:
  2277.             e = None
  2278.             logging.warning("KeyError in getTemplateVariable ('updateSearchString')")
  2279.  
  2280.  
  2281.     
  2282.     def toggleDownloadsView(self):
  2283.         
  2284.         try:
  2285.             self.templateHandle.getTemplateVariable('toggleDownloadsView')(self.templateHandle)
  2286.         except KeyError:
  2287.             e = None
  2288.             logging.warning("KeyError in getTemplateVariable ('toggleDownloadsView')")
  2289.  
  2290.  
  2291.     
  2292.     def toggleWatchableView(self):
  2293.         
  2294.         try:
  2295.             self.templateHandle.getTemplateVariable('toggleWatchableView')(self.templateHandle)
  2296.         except KeyError:
  2297.             e = None
  2298.             logging.warning("KeyError in getTemplateVariable ('toggleWatchableView')")
  2299.  
  2300.  
  2301.     
  2302.     def toggleNewItemsView(self):
  2303.         
  2304.         try:
  2305.             self.templateHandle.getTemplateVariable('toggleNewItemsView')(self.templateHandle)
  2306.         except KeyError:
  2307.             e = None
  2308.             logging.warning("KeyError in getTemplateVariable ('toggleNewItemsView')")
  2309.  
  2310.  
  2311.     
  2312.     def toggleAllItemsMode(self):
  2313.         
  2314.         try:
  2315.             self.templateHandle.getTemplateVariable('toggleAllItemsMode')(self.templateHandle)
  2316.         except KeyError:
  2317.             e = None
  2318.             logging.warning("KeyError in getTemplateVariable ('toggleAllItemsMode')")
  2319.  
  2320.  
  2321.     
  2322.     def pauseDownloads(self):
  2323.         
  2324.         try:
  2325.             view = self.templateHandle.getTemplateVariable('allDownloadingItems')
  2326.         except KeyError:
  2327.             e = None
  2328.             logging.warning("KeyError in getTemplateVariable ('allDownloadingItems') during pauseDownloads()")
  2329.             return None
  2330.  
  2331.         for item in view:
  2332.             item.pause()
  2333.         
  2334.  
  2335.     
  2336.     def resumeDownloads(self):
  2337.         
  2338.         try:
  2339.             view = self.templateHandle.getTemplateVariable('allDownloadingItems')
  2340.         except KeyError:
  2341.             e = None
  2342.             logging.warning("KeyError in getTemplateVariable ('allDownloadingItems') during resumeDownloads()")
  2343.             return None
  2344.  
  2345.         for item in view:
  2346.             item.resume()
  2347.         
  2348.  
  2349.     
  2350.     def cancelDownloads(self):
  2351.         
  2352.         try:
  2353.             view = self.templateHandle.getTemplateVariable('allDownloadingItems')
  2354.         except KeyError:
  2355.             e = None
  2356.             logging.warning("KeyError in getTemplateVariable ('allDownloadingItems') during cancelDownloads()")
  2357.             return None
  2358.  
  2359.         for item in view:
  2360.             item.expire()
  2361.         
  2362.  
  2363.     
  2364.     def playViewNamed(self, viewName, firstItemId):
  2365.         
  2366.         try:
  2367.             view = self.templateHandle.getTemplateVariable(viewName)
  2368.         except KeyError:
  2369.             e = None
  2370.             logging.warning('KeyError in getTemplateVariable (%s) during playViewNamed()' % (viewName,))
  2371.             return None
  2372.  
  2373.         controller.playView(view, firstItemId)
  2374.  
  2375.     
  2376.     def playOneItem(self, viewName, itemID):
  2377.         
  2378.         try:
  2379.             view = self.templateHandle.getTemplateVariable(viewName)
  2380.         except KeyError:
  2381.             e = None
  2382.             logging.warning('KeyError in getTemplateVariable (%s) during playOneItem()' % (viewName,))
  2383.             return None
  2384.  
  2385.         controller.playView(view, itemID, justPlayOne = True)
  2386.  
  2387.     
  2388.     def playNewVideos(self, id):
  2389.         
  2390.         try:
  2391.             obj = db.getObjectByID(int(id))
  2392.         except database.ObjectNotFoundError:
  2393.             return None
  2394.  
  2395.         
  2396.         def myUnwatchedItems(obj):
  2397.             if obj.getState() == u'newly-downloaded' and not obj.isNonVideoFile():
  2398.                 pass
  2399.             return not (obj.isContainerItem)
  2400.  
  2401.         controller.selection.selectTabByObject(obj, displayTabContent = False)
  2402.         if isinstance(obj, feed.Feed):
  2403.             feedView = views.items.filterWithIndex(indexes.itemsByFeed, obj.getID())
  2404.             view = feedView.filter(myUnwatchedItems, sortFunc = sorts.item)
  2405.             controller.playView(view)
  2406.             view.unlink()
  2407.         elif isinstance(obj, folder.ChannelFolder):
  2408.             folderView = views.items.filterWithIndex(indexes.itemsByChannelFolder, obj)
  2409.             view = folderView.filter(myUnwatchedItems, sortFunc = sorts.item)
  2410.             controller.playView(view)
  2411.             view.unlink()
  2412.         elif isinstance(obj, tabs.StaticTab):
  2413.             view = views.unwatchedItems
  2414.             controller.playView(view)
  2415.         else:
  2416.             raise TypeError("Can't get new videos for %s (type: %s)" % (obj, type(obj)))
  2417.  
  2418.     
  2419.     def playItemExternally(self, itemID):
  2420.         controller.playbackController.playItemExternally(itemID)
  2421.  
  2422.     
  2423.     def skipItem(self, itemID):
  2424.         controller.playbackController.skip(1)
  2425.  
  2426.     
  2427.     def updateLastSearchEngine(self, engine):
  2428.         (searchFeed, searchDownloadsFeed) = self._TemplateActionHandler__getSearchFeeds()
  2429.         if searchFeed is not None:
  2430.             searchFeed.lastEngine = engine
  2431.         
  2432.  
  2433.     
  2434.     def updateLastSearchQuery(self, query):
  2435.         (searchFeed, searchDownloadsFeed) = self._TemplateActionHandler__getSearchFeeds()
  2436.         if searchFeed is not None:
  2437.             searchFeed.lastQuery = query
  2438.         
  2439.  
  2440.     
  2441.     def performSearch(self, engine, query):
  2442.         util.checkU(engine)
  2443.         util.checkU(query)
  2444.         (searchFeed, searchDownloadsFeed) = self._TemplateActionHandler__getSearchFeeds()
  2445.         if searchFeed is not None and searchDownloadsFeed is not None:
  2446.             searchFeed.preserveDownloads(searchDownloadsFeed)
  2447.             searchFeed.lookup(engine, query)
  2448.         
  2449.  
  2450.     
  2451.     def resetSearch(self):
  2452.         (searchFeed, searchDownloadsFeed) = self._TemplateActionHandler__getSearchFeeds()
  2453.         if searchFeed is not None and searchDownloadsFeed is not None:
  2454.             searchFeed.preserveDownloads(searchDownloadsFeed)
  2455.             searchFeed.reset()
  2456.         
  2457.  
  2458.     
  2459.     def sortBy(self, by, section):
  2460.         
  2461.         try:
  2462.             self.templateHandle.getTemplateVariable('setSortBy')(by, section, self.templateHandle)
  2463.         except KeyError:
  2464.             e = None
  2465.             logging.warning("KeyError in getTemplateVariable ('setSortBy')")
  2466.  
  2467.  
  2468.     
  2469.     def handleSelect(self, area, viewName, id, shiftDown, ctrlDown):
  2470.         
  2471.         try:
  2472.             view = self.templateHandle.getTemplateVariable(viewName)
  2473.         except KeyError:
  2474.             e = None
  2475.             logging.warning('KeyError in getTemplateVariable (%s) during handleSelect()' % (viewName,))
  2476.             return None
  2477.  
  2478.         shift = shiftDown == '1'
  2479.         ctrl = ctrlDown == '1'
  2480.         controller.selection.selectItem(area, view, int(id), shift, ctrl)
  2481.  
  2482.     
  2483.     def handleContextMenuSelect(self, id, area, viewName):
  2484.         
  2485.         try:
  2486.             obj = db.getObjectByID(int(id))
  2487.         except:
  2488.             traceback.print_exc()
  2489.  
  2490.         
  2491.         try:
  2492.             view = self.templateHandle.getTemplateVariable(viewName)
  2493.         except KeyError:
  2494.             e = None
  2495.             logging.warning('KeyError in getTemplateVariable (%s) during handleContextMenuSelect()' % (viewName,))
  2496.             return None
  2497.  
  2498.         if not controller.selection.isSelected(area, view, int(id)):
  2499.             self.handleSelect(area, viewName, id, False, False)
  2500.         
  2501.         popup = menu.makeContextMenu(self.currentName, view, controller.selection.getSelectionForArea(area), int(id))
  2502.         if popup:
  2503.             delegate.showContextMenu(popup)
  2504.         
  2505.  
  2506.     
  2507.     def __getSearchFeeds(self):
  2508.         searchFeed = controller.getGlobalFeed('dtv:search')
  2509.         if not searchFeed is not None:
  2510.             raise AssertionError
  2511.         searchDownloadsFeed = controller.getGlobalFeed('dtv:searchDownloads')
  2512.         if not searchDownloadsFeed is not None:
  2513.             raise AssertionError
  2514.         return (searchFeed, searchDownloadsFeed)
  2515.  
  2516.     
  2517.     def setVolume(self, level):
  2518.         pass
  2519.  
  2520.     
  2521.     def setVideoProgress(self, pos):
  2522.         pass
  2523.  
  2524.  
  2525.  
  2526. def stringToBoolean(string):
  2527.     if string == '' and string == '0' or string == 'false':
  2528.         return False
  2529.     else:
  2530.         return True
  2531.  
  2532.  
  2533. class Playlist:
  2534.     
  2535.     def __init__(self, view, firstItemId):
  2536.         self.initialView = view
  2537.         self.filteredView = self.initialView.filter(mappableToPlaylistItem)
  2538.         self.view = self.filteredView.map(mapToPlaylistItem)
  2539.         self.view.confirmDBThread()
  2540.         self.view.resetCursor()
  2541.         while True:
  2542.             cur = self.view.getNext()
  2543.             if cur == None:
  2544.                 self.view.resetCursor()
  2545.                 self.view.getNext()
  2546.                 break
  2547.             
  2548.             if firstItemId is None or cur.getID() == int(firstItemId):
  2549.                 break
  2550.                 continue
  2551.  
  2552.     
  2553.     def reset(self):
  2554.         self.initialView.removeView(self.filteredView)
  2555.         self.initialView = None
  2556.         self.filteredView = None
  2557.         self.view = None
  2558.  
  2559.     
  2560.     def cur(self):
  2561.         return self.itemMarkedAsViewed(self.view.cur())
  2562.  
  2563.     
  2564.     def getNext(self):
  2565.         return self.itemMarkedAsViewed(self.view.getNext())
  2566.  
  2567.     
  2568.     def getPrev(self):
  2569.         return self.itemMarkedAsViewed(self.view.getPrev())
  2570.  
  2571.     
  2572.     def itemMarkedAsViewed(self, anItem):
  2573.         if anItem is not None:
  2574.             eventloop.addIdle(anItem.onViewed, 'Mark item viewed')
  2575.         
  2576.         return anItem
  2577.  
  2578.  
  2579.  
  2580. class PlaylistItemFromItem:
  2581.     
  2582.     def __init__(self, anItem):
  2583.         self.item = anItem
  2584.         self.dcOnViewed = None
  2585.  
  2586.     
  2587.     def getTitle(self):
  2588.         return self.item.getTitle()
  2589.  
  2590.     
  2591.     def getVideoFilename(self):
  2592.         return self.item.getVideoFilename()
  2593.  
  2594.     
  2595.     def getLength(self):
  2596.         return 42.42
  2597.  
  2598.     
  2599.     def onViewedExecute(self):
  2600.         if self.item.idExists():
  2601.             self.item.markItemSeen()
  2602.         
  2603.         self.dcOnViewed = None
  2604.  
  2605.     
  2606.     def onViewed(self):
  2607.         if self.dcOnViewed or self.item.getSeen():
  2608.             return None
  2609.         
  2610.         self.dcOnViewed = eventloop.addTimeout(5, self.onViewedExecute, 'Mark item viewed')
  2611.  
  2612.     
  2613.     def onViewedCancel(self):
  2614.         if self.dcOnViewed:
  2615.             self.dcOnViewed.cancel()
  2616.             self.dcOnViewed = None
  2617.         
  2618.  
  2619.     
  2620.     def getID(self):
  2621.         return self.item.getID()
  2622.  
  2623.     
  2624.     def __getattr__(self, attr):
  2625.         return getattr(self.item, attr)
  2626.  
  2627.  
  2628.  
  2629. def mappableToPlaylistItem(obj):
  2630.     if isinstance(obj, item.Item):
  2631.         pass
  2632.     return obj.isDownloaded()
  2633.  
  2634.  
  2635. def mapToPlaylistItem(obj):
  2636.     return PlaylistItemFromItem(obj)
  2637.  
  2638.  
  2639. def _defaultFeeds():
  2640.     if config.get(prefs.DEFAULT_CHANNELS_FILE) is not None:
  2641.         importer = opml.Importer()
  2642.         
  2643.         try:
  2644.             if config.get(prefs.THEME_NAME) is not None and config.get(prefs.THEME_DIRECTORY) is not None:
  2645.                 filepath = os.path.join(config.get(prefs.THEME_DIRECTORY), config.get(prefs.THEME_NAME), config.get(prefs.DEFAULT_CHANNELS_FILE))
  2646.             else:
  2647.                 filepath = os.path.join(config.get(prefs.SUPPORT_DIRECTORY), config.get(prefs.DEFAULT_CHANNELS_FILE))
  2648.             importer.importSubscriptionsFrom(filepath, showSummary = False)
  2649.             logging.info('Imported %s' % filepath)
  2650.         except:
  2651.             logging.warn('Could not import %s' % filepath)
  2652.  
  2653.         return None
  2654.     
  2655.     logging.info('Adding default feeds')
  2656.     if platform.system() == 'Darwin':
  2657.         defaultFeedURLs = [
  2658.             u'http://www.getmiro.com/screencasts/mac/mac.feed.rss']
  2659.     elif platform.system() == 'Windows':
  2660.         defaultFeedURLs = [
  2661.             u'http://www.getmiro.com/screencasts/windows/win.feed.rss']
  2662.     else:
  2663.         defaultFeedURLs = [
  2664.             u'http://www.getmiro.com/screencasts/windows/win.feed.rss']
  2665.     defaultFeedURLs.extend([
  2666.         (_('Starter Channels'), [
  2667.             u'http://richie-b.blip.tv/posts/?skin=rss',
  2668.             u'http://feeds.pbs.org/pbs/kcet/wiredscience-video',
  2669.             u'http://www.jpl.nasa.gov/multimedia/rss/podfeed-hd.xml',
  2670.             u'http://www.linktv.org/rss/hq/mosaic.xml'])])
  2671.     for default in defaultFeedURLs:
  2672.         print repr(default)
  2673.         if isinstance(default, tuple):
  2674.             defaultFolder = default
  2675.             c_folder = folder.ChannelFolder(defaultFolder[0])
  2676.             for url in defaultFolder[1]:
  2677.                 d_feed = feed.Feed(url, initiallyAutoDownloadable = False)
  2678.                 d_feed.setFolder(c_folder)
  2679.             
  2680.         d_feed = feed.Feed(default, initiallyAutoDownloadable = False)
  2681.     
  2682.     playlist.SavedPlaylist(_(u'Example Playlist'))
  2683.  
  2684.  
  2685. def _getThemeHistory():
  2686.     if len(views.themeHistories) > 0:
  2687.         return views.themeHistories[0]
  2688.     else:
  2689.         return theme.ThemeHistory()
  2690.  
  2691.  
  2692. def _getInitialChannelGuide():
  2693.     default_guide = None
  2694.     newGuide = False
  2695.     for guideObj in views.guides:
  2696.         if default_guide is None:
  2697.             if guideObj.getDefault():
  2698.                 default_guide = guideObj
  2699.             
  2700.         guideObj.getDefault()
  2701.     
  2702.     if default_guide is None:
  2703.         newGuide = True
  2704.         logging.info('Spawning Miro Guide...')
  2705.         default_guide = guide.ChannelGuide()
  2706.         initialFeeds = resources.path('initial-feeds.democracy')
  2707.         if os.path.exists(initialFeeds):
  2708.             urls = subscription.parseFile(initialFeeds)
  2709.             if urls is not None:
  2710.                 for url in urls:
  2711.                     feed.Feed(url, initiallyAutoDownloadable = False)
  2712.                 
  2713.             
  2714.             dialog = dialogs.MessageBoxDialog(_('Custom Channels'), Template(_('You are running a version of $longAppName with a custom set of channels.')).substitute(longAppName = config.get(prefs.LONG_APP_NAME)))
  2715.             dialog.run()
  2716.             controller.initial_feeds = True
  2717.         else:
  2718.             _defaultFeeds()
  2719.     
  2720.     return (newGuide, default_guide)
  2721.  
  2722.  
  2723. def changeMoviesDirectory(newDir, migrate):
  2724.     if not util.directoryWritable(newDir):
  2725.         dialog = dialogs.MessageBoxDialog(_('Error Changing Movies Directory'), _("You don't have permission to write to the directory you selected.  Miro will continue to use the old videos directory."))
  2726.         dialog.run()
  2727.         return None
  2728.     
  2729.     oldDir = config.get(prefs.MOVIES_DIRECTORY)
  2730.     config.set(prefs.MOVIES_DIRECTORY, newDir)
  2731.     if migrate:
  2732.         views.remoteDownloads.confirmDBThread()
  2733.         for download in views.remoteDownloads:
  2734.             if download.isFinished():
  2735.                 logging.info('migrating %s', download.getFilename())
  2736.                 download.migrate(newDir)
  2737.                 continue
  2738.         
  2739.         
  2740.         try:
  2741.             os.rmdir(os.path.join(oldDir, 'Incomplete Downloads'))
  2742.         except:
  2743.             pass
  2744.  
  2745.         
  2746.         try:
  2747.             os.rmdir(oldDir)
  2748.  
  2749.     
  2750.     util.getSingletonDDBObject(views.directoryFeed).update()
  2751.  
  2752. changeMoviesDirectory = eventloop.asUrgent(changeMoviesDirectory)
  2753.  
  2754. def saveVideo(currentPath, savePath):
  2755.     logging.info('saving video %s to %s' % (currentPath, savePath))
  2756.     
  2757.     try:
  2758.         shutil.copyfile(currentPath, savePath)
  2759.     except:
  2760.         title = _('Error Saving Video')
  2761.         name = os.path.basename(currentPath)
  2762.         text = _('An error occured while trying to save %s.  Please check that the file has not been deleted and try again.') % util.clampText(name, 50)
  2763.         dialogs.MessageBoxDialog(title, text).run()
  2764.         logging.warn('Error saving video: %s' % traceback.format_exc())
  2765.  
  2766.  
  2767. saveVideo = eventloop.asUrgent(saveVideo)
  2768.